@meshagent/meshagent 0.37.2 → 0.38.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/CHANGELOG.md +3 -0
- package/dist/browser/agent.js +74 -10
- package/dist/browser/developer-client.js +3 -0
- package/dist/browser/helpers.d.ts +2 -2
- package/dist/browser/helpers.js +1 -1
- package/dist/browser/meshagent-client.d.ts +25 -0
- package/dist/browser/meshagent-client.js +65 -0
- package/dist/browser/messaging-client.d.ts +29 -16
- package/dist/browser/messaging-client.js +256 -154
- package/dist/browser/participant.d.ts +7 -2
- package/dist/browser/participant.js +9 -9
- package/dist/browser/protocol.d.ts +85 -28
- package/dist/browser/protocol.js +356 -119
- package/dist/browser/room-client.d.ts +165 -29
- package/dist/browser/room-client.js +1114 -74
- package/dist/browser/room-event.d.ts +11 -0
- package/dist/browser/room-event.js +21 -1
- package/dist/browser/room-server-client.d.ts +2 -0
- package/dist/browser/room-server-client.js +6 -0
- package/dist/browser/runtime.d.ts +1 -1
- package/dist/browser/runtime.js +3 -1
- package/dist/browser/secrets-client.js +6 -2
- package/dist/browser/storage-client.d.ts +1 -0
- package/dist/browser/storage-client.js +9 -0
- package/dist/browser/sync-client.d.ts +16 -14
- package/dist/browser/sync-client.js +195 -116
- package/dist/esm/agent.js +74 -10
- package/dist/esm/developer-client.js +3 -0
- package/dist/esm/helpers.d.ts +2 -2
- package/dist/esm/helpers.js +1 -1
- package/dist/esm/meshagent-client.d.ts +25 -0
- package/dist/esm/meshagent-client.js +65 -0
- package/dist/esm/messaging-client.d.ts +29 -16
- package/dist/esm/messaging-client.js +256 -154
- package/dist/esm/participant.d.ts +7 -2
- package/dist/esm/participant.js +9 -9
- package/dist/esm/protocol.d.ts +85 -28
- package/dist/esm/protocol.js +352 -118
- package/dist/esm/room-client.d.ts +165 -29
- package/dist/esm/room-client.js +1112 -73
- package/dist/esm/room-event.d.ts +11 -0
- package/dist/esm/room-event.js +19 -0
- package/dist/esm/room-server-client.d.ts +2 -0
- package/dist/esm/room-server-client.js +7 -1
- package/dist/esm/runtime.d.ts +1 -1
- package/dist/esm/runtime.js +1 -1
- package/dist/esm/secrets-client.js +6 -2
- package/dist/esm/storage-client.d.ts +1 -0
- package/dist/esm/storage-client.js +9 -0
- package/dist/esm/sync-client.d.ts +16 -14
- package/dist/esm/sync-client.js +196 -117
- package/dist/node/agent.js +74 -10
- package/dist/node/developer-client.js +3 -0
- package/dist/node/helpers.d.ts +2 -2
- package/dist/node/helpers.js +1 -1
- package/dist/node/meshagent-client.d.ts +25 -0
- package/dist/node/meshagent-client.js +65 -0
- package/dist/node/messaging-client.d.ts +29 -16
- package/dist/node/messaging-client.js +256 -154
- package/dist/node/participant.d.ts +7 -2
- package/dist/node/participant.js +9 -9
- package/dist/node/protocol.d.ts +85 -28
- package/dist/node/protocol.js +356 -119
- package/dist/node/room-client.d.ts +165 -29
- package/dist/node/room-client.js +1114 -74
- package/dist/node/room-event.d.ts +11 -0
- package/dist/node/room-event.js +21 -1
- package/dist/node/room-server-client.d.ts +2 -0
- package/dist/node/room-server-client.js +6 -0
- package/dist/node/runtime.d.ts +1 -1
- package/dist/node/runtime.js +3 -1
- package/dist/node/secrets-client.js +6 -2
- package/dist/node/storage-client.d.ts +1 -0
- package/dist/node/storage-client.js +9 -0
- package/dist/node/sync-client.d.ts +16 -14
- package/dist/node/sync-client.js +195 -116
- package/package.json +6 -3
package/dist/esm/room-client.js
CHANGED
|
@@ -1,32 +1,244 @@
|
|
|
1
1
|
import { Completer } from "./completer";
|
|
2
|
-
import {
|
|
3
|
-
import { LocalParticipant } from "./participant";
|
|
4
|
-
import { StreamController } from "./stream-controller";
|
|
5
|
-
import { SyncClient } from "./sync-client";
|
|
2
|
+
import { DatabaseClient } from "./database-client";
|
|
6
3
|
import { DeveloperClient } from "./developer-client";
|
|
7
|
-
import {
|
|
4
|
+
import { EventEmitter } from "./event-emitter";
|
|
8
5
|
import { MessagingClient } from "./messaging-client";
|
|
6
|
+
import { MemoryClient } from "./memory-client";
|
|
7
|
+
import { LocalParticipant } from "./participant";
|
|
8
|
+
import { ProtocolCloseException, ProtocolCloseKind, ProtocolHandshakeException, ProtocolReconnectUnsupportedException, WebSocketClientProtocol, } from "./protocol";
|
|
9
9
|
import { QueuesClient } from "./queues-client";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { BinaryContent, ControlContent, EmptyContent, ErrorContent, FileContent, JsonContent, LinkContent, TextContent, unpackContent } from "./response";
|
|
11
|
+
import { RoomStatusEvent } from "./room-event";
|
|
12
|
+
import { RoomServerException } from "./room-server-client";
|
|
12
13
|
import { SecretsClient } from "./secrets-client";
|
|
13
|
-
import { ContainersClient } from "./containers-client";
|
|
14
|
-
import { MemoryClient } from "./memory-client";
|
|
15
14
|
import { ServicesClient } from "./services-client";
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
15
|
+
import { StorageClient } from "./storage-client";
|
|
16
|
+
import { StreamController } from "./stream-controller";
|
|
17
|
+
import { SyncClient } from "./sync-client";
|
|
18
|
+
import { splitMessageHeader, splitMessagePayload, packMessage, unpackMessage } from "./utils";
|
|
19
|
+
import { AgentsClient, ToolkitDescription } from "./agent-client";
|
|
20
|
+
import { ContainersClient } from "./containers-client";
|
|
21
|
+
class ProtocolStartupFailure extends Error {
|
|
22
|
+
constructor({ kind, reason }) {
|
|
23
|
+
super(reason ?? kind);
|
|
24
|
+
this.name = "ProtocolStartupFailure";
|
|
25
|
+
this.kind = kind;
|
|
26
|
+
this.reason = reason;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
class RoomClientTerminalState {
|
|
30
|
+
constructor({ requestMessage, toolCallMessage, messageSendMessage, }) {
|
|
31
|
+
this.requestMessage = requestMessage;
|
|
32
|
+
this.toolCallMessage = toolCallMessage;
|
|
33
|
+
this.messageSendMessage = messageSendMessage;
|
|
34
|
+
}
|
|
35
|
+
requestError() {
|
|
36
|
+
return new RoomServerException(this.requestMessage);
|
|
37
|
+
}
|
|
38
|
+
toolCallError() {
|
|
39
|
+
return new RoomServerException(this.toolCallMessage);
|
|
40
|
+
}
|
|
41
|
+
messageSendError() {
|
|
42
|
+
return new RoomServerException(this.messageSendMessage);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
class RoomConnectionStatusException extends RoomServerException {
|
|
46
|
+
constructor({ statusCode, statusText, }) {
|
|
47
|
+
const normalizedStatusText = statusText?.trim();
|
|
48
|
+
super(normalizedStatusText == null || normalizedStatusText.length === 0
|
|
49
|
+
? `websocket connect failed with status ${statusCode}`
|
|
50
|
+
: `websocket connect failed with status ${statusCode}: ${normalizedStatusText}`);
|
|
51
|
+
this.name = "RoomConnectionStatusException";
|
|
52
|
+
this.statusCode = statusCode;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function normalizeCloseReason(reason) {
|
|
56
|
+
if (reason == null) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const normalized = reason.trim();
|
|
60
|
+
return normalized.length === 0 ? null : normalized;
|
|
61
|
+
}
|
|
62
|
+
function wrapRoomConnectionError(error) {
|
|
63
|
+
if (error instanceof RoomServerException) {
|
|
64
|
+
return error;
|
|
65
|
+
}
|
|
66
|
+
if (error instanceof ProtocolHandshakeException) {
|
|
67
|
+
return new RoomConnectionStatusException({
|
|
68
|
+
statusCode: error.statusCode,
|
|
69
|
+
statusText: error.statusText,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if (error instanceof ProtocolCloseException) {
|
|
73
|
+
return new RoomServerException(normalizeCloseReason(error.reason) ?? `room connection closed with status ${error.closeCode}`);
|
|
74
|
+
}
|
|
75
|
+
return new RoomServerException(`room connection error: ${String(error)}`);
|
|
76
|
+
}
|
|
77
|
+
function nonRetryableConnectFailureReason(error) {
|
|
78
|
+
if (error instanceof RoomConnectionStatusException
|
|
79
|
+
&& (error.statusCode === 403 || error.statusCode === 404)) {
|
|
80
|
+
return error.message;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
function roomClosedBeforeReadyError(protocol) {
|
|
85
|
+
return new RoomServerException(normalizeCloseReason(protocol.closeReason) ?? "room connection closed before request completed");
|
|
86
|
+
}
|
|
87
|
+
function getEnvironmentValue(name) {
|
|
88
|
+
if (typeof process === "undefined") {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
return process.env?.[name];
|
|
92
|
+
}
|
|
93
|
+
function websocketRoomUrlFromEnvironment(roomName) {
|
|
94
|
+
const configuredBaseUrl = getEnvironmentValue("MESHAGENT_ROOM_URL")
|
|
95
|
+
?? getEnvironmentValue("MESHAGENT_API_URL")
|
|
96
|
+
?? "wss://api.meshagent.com";
|
|
97
|
+
let baseUrl = configuredBaseUrl;
|
|
98
|
+
if (baseUrl.startsWith("https:")) {
|
|
99
|
+
baseUrl = `wss:${baseUrl.slice("https:".length)}`;
|
|
100
|
+
}
|
|
101
|
+
else if (baseUrl.startsWith("http:")) {
|
|
102
|
+
baseUrl = `ws:${baseUrl.slice("http:".length)}`;
|
|
103
|
+
}
|
|
104
|
+
return `${baseUrl}/rooms/${roomName}`;
|
|
105
|
+
}
|
|
106
|
+
function createProtocolFactoryFromEnvironment() {
|
|
107
|
+
const roomName = getEnvironmentValue("MESHAGENT_ROOM");
|
|
108
|
+
const token = getEnvironmentValue("MESHAGENT_TOKEN");
|
|
109
|
+
if (roomName == null || roomName.trim().length === 0 || token == null || token.trim().length === 0) {
|
|
110
|
+
throw new Error("protocolFactory must be configured or MESHAGENT_ROOM and MESHAGENT_TOKEN must be set in the environment");
|
|
111
|
+
}
|
|
112
|
+
return WebSocketClientProtocol.createFactory({
|
|
113
|
+
url: websocketRoomUrlFromEnvironment(roomName),
|
|
114
|
+
token,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
export class RoomProtocolProxy {
|
|
118
|
+
constructor({ room }) {
|
|
119
|
+
this._handlers = new Map();
|
|
120
|
+
this._room = room;
|
|
121
|
+
}
|
|
122
|
+
_bind(protocol) {
|
|
123
|
+
for (const [type, handler] of this._handlers.entries()) {
|
|
124
|
+
if (protocol.getHandler(type) === handler) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
protocol.addHandler(type, handler);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
_unbind(protocol) {
|
|
131
|
+
for (const [type, handler] of this._handlers.entries()) {
|
|
132
|
+
const current = protocol.getHandler(type);
|
|
133
|
+
if (current === handler) {
|
|
134
|
+
protocol.removeHandler(type, handler);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
addHandler(type, handler) {
|
|
139
|
+
if (this._handlers.has(type)) {
|
|
140
|
+
throw new Error(`already registered handler for ${type}`);
|
|
141
|
+
}
|
|
142
|
+
this._handlers.set(type, handler);
|
|
143
|
+
this._bind(this._room._protocolInstance);
|
|
144
|
+
}
|
|
145
|
+
removeHandler(type, handler) {
|
|
146
|
+
const registered = this._handlers.get(type);
|
|
147
|
+
if (registered !== handler) {
|
|
148
|
+
throw new Error(`handler mismatch for ${type}`);
|
|
149
|
+
}
|
|
150
|
+
this._handlers.delete(type);
|
|
151
|
+
if (this._room._protocolInstance.getHandler(type) === handler) {
|
|
152
|
+
this._room._protocolInstance.removeHandler(type, handler);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
getHandler(type) {
|
|
156
|
+
return this._handlers.get(type);
|
|
157
|
+
}
|
|
158
|
+
async send(type, data, { id } = {}) {
|
|
159
|
+
if (this._room._entered && !this._room.isConnected && !this._room._allowDisconnectedRequests) {
|
|
160
|
+
throw this._room._disconnectedError({ baseMessage: "room connection is disconnected" });
|
|
161
|
+
}
|
|
162
|
+
await this._room._protocolInstance.send(type, data, id);
|
|
163
|
+
}
|
|
164
|
+
sendNowait(type, data, { id } = {}) {
|
|
165
|
+
if (this._room._entered && !this._room.isConnected && !this._room._allowDisconnectedRequests) {
|
|
166
|
+
throw this._room._disconnectedError({ baseMessage: "room connection is disconnected" });
|
|
167
|
+
}
|
|
168
|
+
return this._room._protocolInstance.sendNowait(type, data, { id });
|
|
169
|
+
}
|
|
170
|
+
getNextMessageId() {
|
|
171
|
+
if (this._room._entered && !this._room.isConnected && !this._room._allowDisconnectedRequests) {
|
|
172
|
+
throw this._room._disconnectedError({ baseMessage: "room connection is disconnected" });
|
|
173
|
+
}
|
|
174
|
+
return this._room._protocolInstance.getNextMessageId();
|
|
175
|
+
}
|
|
176
|
+
get done() {
|
|
177
|
+
return this._room.waitForClose();
|
|
178
|
+
}
|
|
179
|
+
async waitForClose() {
|
|
180
|
+
await this._room.waitForClose();
|
|
181
|
+
}
|
|
182
|
+
get closeKind() {
|
|
183
|
+
return this._room.closeKind;
|
|
184
|
+
}
|
|
185
|
+
get closeReason() {
|
|
186
|
+
return this._room.closeReason;
|
|
187
|
+
}
|
|
188
|
+
get isOpen() {
|
|
189
|
+
return this._room._protocolInstance.isOpen;
|
|
190
|
+
}
|
|
191
|
+
get isClosed() {
|
|
192
|
+
return this._room.isClosed;
|
|
193
|
+
}
|
|
194
|
+
get token() {
|
|
195
|
+
return this._room._protocolInstance.token;
|
|
196
|
+
}
|
|
197
|
+
get url() {
|
|
198
|
+
return this._room._protocolInstance.url;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
18
201
|
export class RoomClient {
|
|
19
|
-
constructor({
|
|
202
|
+
constructor({ protocolFactory = null, reconnectTimeout = null, } = {}) {
|
|
203
|
+
this._entered = false;
|
|
204
|
+
this._allowDisconnectedRequests = false;
|
|
205
|
+
this._eventsController = new StreamController();
|
|
206
|
+
this._eventEmitter = new EventEmitter();
|
|
20
207
|
this._pendingRequests = new Map();
|
|
208
|
+
this._toolCallStreams = new Map();
|
|
209
|
+
this._ignoredResponseLabels = new Map();
|
|
21
210
|
this._ready = new Completer();
|
|
211
|
+
this._roomClosed = new Completer();
|
|
212
|
+
this._connectionReady = new Completer();
|
|
213
|
+
this._localParticipantReady = new Completer();
|
|
214
|
+
this._connected = false;
|
|
215
|
+
this._closing = false;
|
|
22
216
|
this._localParticipant = null;
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
25
|
-
this.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
217
|
+
this._lifecycleTask = null;
|
|
218
|
+
this._terminalState = null;
|
|
219
|
+
this._closeKind = null;
|
|
220
|
+
this._closeReason = null;
|
|
221
|
+
this._terminalCallbacksInvoked = false;
|
|
222
|
+
this._roomName = null;
|
|
223
|
+
this._roomUrl = null;
|
|
224
|
+
this._sessionId = null;
|
|
225
|
+
this._handleRoomReadyBound = this._handleRoomReady.bind(this);
|
|
226
|
+
this._handleRoomStatusBound = this._handleRoomStatus.bind(this);
|
|
227
|
+
this._handleParticipantBound = this._handleParticipant.bind(this);
|
|
228
|
+
this._handleResponseBound = this._handleResponse.bind(this);
|
|
229
|
+
this._handleToolCallResponseChunkBound = this._handleToolCallResponseChunk.bind(this);
|
|
230
|
+
if (reconnectTimeout != null && reconnectTimeout < 0) {
|
|
231
|
+
throw new Error("reconnectTimeout must be null or non-negative");
|
|
232
|
+
}
|
|
233
|
+
this._protocolFactory = protocolFactory ?? createProtocolFactoryFromEnvironment();
|
|
234
|
+
this._reconnectTimeout = reconnectTimeout;
|
|
235
|
+
this._protocolInstance = this._protocolFactory();
|
|
236
|
+
this.protocol = new RoomProtocolProxy({ room: this });
|
|
237
|
+
this.protocol.addHandler("room_ready", this._handleRoomReadyBound);
|
|
238
|
+
this.protocol.addHandler("room.status", this._handleRoomStatusBound);
|
|
239
|
+
this.protocol.addHandler("connected", this._handleParticipantBound);
|
|
240
|
+
this.protocol.addHandler("__response__", this._handleResponseBound);
|
|
241
|
+
this.protocol.addHandler("room.tool_call_response_chunk", this._handleToolCallResponseChunkBound);
|
|
30
242
|
this.sync = new SyncClient({ room: this });
|
|
31
243
|
this.storage = new StorageClient({ room: this });
|
|
32
244
|
this.developer = new DeveloperClient({ room: this });
|
|
@@ -45,33 +257,820 @@ export class RoomClient {
|
|
|
45
257
|
get ready() {
|
|
46
258
|
return this._ready.fut;
|
|
47
259
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
this.sync.start({ onDone, onError });
|
|
51
|
-
await this.ready;
|
|
260
|
+
get isConnected() {
|
|
261
|
+
return this._connected;
|
|
52
262
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
263
|
+
get isClosed() {
|
|
264
|
+
return this._closing || this._terminalState != null || this._roomClosed.completed;
|
|
265
|
+
}
|
|
266
|
+
get isClosing() {
|
|
267
|
+
return this._closing;
|
|
268
|
+
}
|
|
269
|
+
get closeKind() {
|
|
270
|
+
return this._closeKind ?? this._protocolInstance.closeKind;
|
|
271
|
+
}
|
|
272
|
+
get closeReason() {
|
|
273
|
+
return this._closeReason ?? normalizeCloseReason(this._protocolInstance.closeReason);
|
|
274
|
+
}
|
|
275
|
+
get roomName() {
|
|
276
|
+
return this._roomName;
|
|
277
|
+
}
|
|
278
|
+
get roomUrl() {
|
|
279
|
+
return this._roomUrl;
|
|
280
|
+
}
|
|
281
|
+
get sessionId() {
|
|
282
|
+
return this._sessionId;
|
|
283
|
+
}
|
|
284
|
+
isActiveProtocol(protocol) {
|
|
285
|
+
return protocol === this._protocolInstance;
|
|
286
|
+
}
|
|
287
|
+
on(eventName, callback) {
|
|
288
|
+
this._eventEmitter.on(eventName, callback);
|
|
289
|
+
}
|
|
290
|
+
off(eventName, callback) {
|
|
291
|
+
this._eventEmitter.off(eventName, callback);
|
|
292
|
+
}
|
|
293
|
+
emit(event) {
|
|
294
|
+
this._eventsController.add(event);
|
|
295
|
+
this._eventEmitter.emit(event.name, event);
|
|
296
|
+
}
|
|
297
|
+
listen() {
|
|
298
|
+
return this._eventsController.stream;
|
|
299
|
+
}
|
|
300
|
+
async waitForClose() {
|
|
301
|
+
await this._roomClosed.fut;
|
|
302
|
+
}
|
|
303
|
+
async waitUntilConnected() {
|
|
304
|
+
while (!this._connected) {
|
|
305
|
+
this._raiseIfTerminal();
|
|
306
|
+
if (this._roomClosed.completed) {
|
|
307
|
+
this._raiseIfTerminal();
|
|
308
|
+
throw this._disconnectedError({
|
|
309
|
+
baseMessage: "room connection closed before reconnect completed",
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
58
313
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
314
|
+
}
|
|
315
|
+
async _waitUntilConnectedForMessages() {
|
|
316
|
+
while (!this._connected) {
|
|
317
|
+
this._raiseIfTerminalForMessages();
|
|
318
|
+
if (this._roomClosed.completed) {
|
|
319
|
+
this._raiseIfTerminalForMessages();
|
|
320
|
+
throw this._messageDisconnectedError({
|
|
321
|
+
baseMessage: "room connection closed before message send completed",
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
_markConnected() {
|
|
328
|
+
this._connected = true;
|
|
329
|
+
this._closeKind = null;
|
|
330
|
+
this._closeReason = null;
|
|
331
|
+
}
|
|
332
|
+
_markDisconnected({ reason, kind, }) {
|
|
333
|
+
this._connected = false;
|
|
334
|
+
this._closeKind = kind;
|
|
335
|
+
this._closeReason = normalizeCloseReason(reason);
|
|
336
|
+
this._ignoredResponseLabels.clear();
|
|
337
|
+
}
|
|
338
|
+
_completeRoomClosed() {
|
|
339
|
+
if (!this._roomClosed.completed) {
|
|
340
|
+
this._roomClosed.complete();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
_invokeTerminalCallbacks({ useErrorCallback, error, }) {
|
|
344
|
+
if (this._terminalCallbacksInvoked) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
this._terminalCallbacksInvoked = true;
|
|
348
|
+
if (useErrorCallback) {
|
|
349
|
+
this._errorHandler?.(error);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
this._doneHandler?.();
|
|
353
|
+
}
|
|
354
|
+
_formatClosedMessage({ baseMessage, protocol, closeReason, }) {
|
|
355
|
+
const normalized = normalizeCloseReason(closeReason) ??
|
|
356
|
+
normalizeCloseReason((protocol ?? this._protocolInstance).closeReason);
|
|
357
|
+
if (normalized == null) {
|
|
358
|
+
return baseMessage;
|
|
359
|
+
}
|
|
360
|
+
return `${baseMessage}: ${normalized}`;
|
|
361
|
+
}
|
|
362
|
+
_connectionFailureReason(error) {
|
|
363
|
+
if (error instanceof RoomServerException) {
|
|
364
|
+
return normalizeCloseReason(error.message);
|
|
365
|
+
}
|
|
366
|
+
return normalizeCloseReason(String(error));
|
|
367
|
+
}
|
|
368
|
+
_protocolTerminalState({ protocol } = {}) {
|
|
369
|
+
return new RoomClientTerminalState({
|
|
370
|
+
requestMessage: this._formatClosedMessage({
|
|
371
|
+
baseMessage: "room connection closed before request completed",
|
|
372
|
+
protocol,
|
|
373
|
+
}),
|
|
374
|
+
toolCallMessage: this._formatClosedMessage({
|
|
375
|
+
baseMessage: "room connection closed before tool call completed",
|
|
376
|
+
protocol,
|
|
377
|
+
}),
|
|
378
|
+
messageSendMessage: this._formatClosedMessage({
|
|
379
|
+
baseMessage: "room connection closed before message send completed",
|
|
380
|
+
protocol,
|
|
381
|
+
}),
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
_clientClosedTerminalState() {
|
|
385
|
+
return new RoomClientTerminalState({
|
|
386
|
+
requestMessage: "room client was closed before request completed",
|
|
387
|
+
toolCallMessage: "room client was closed before tool call completed",
|
|
388
|
+
messageSendMessage: "room client was closed before message send completed",
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
_unexpectedCloseTerminalState({ closeReason, }) {
|
|
392
|
+
return new RoomClientTerminalState({
|
|
393
|
+
requestMessage: this._formatClosedMessage({
|
|
394
|
+
baseMessage: "room connection unexpectedly closed before request completed",
|
|
395
|
+
closeReason,
|
|
396
|
+
}),
|
|
397
|
+
toolCallMessage: this._formatClosedMessage({
|
|
398
|
+
baseMessage: "room connection unexpectedly closed before tool call completed",
|
|
399
|
+
closeReason,
|
|
400
|
+
}),
|
|
401
|
+
messageSendMessage: this._formatClosedMessage({
|
|
402
|
+
baseMessage: "room connection unexpectedly closed before message send completed",
|
|
403
|
+
closeReason,
|
|
404
|
+
}),
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
_setStartupTerminalState({ closeKind, closeReason, protocol, }) {
|
|
408
|
+
const normalizedCloseReason = normalizeCloseReason(closeReason);
|
|
409
|
+
this._closeKind = closeKind;
|
|
410
|
+
this._closeReason = normalizedCloseReason;
|
|
411
|
+
if (closeKind === ProtocolCloseKind.ERROR) {
|
|
412
|
+
this._setTerminalState({
|
|
413
|
+
state: this._unexpectedCloseTerminalState({ closeReason: normalizedCloseReason }),
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
else if (closeKind === ProtocolCloseKind.CLIENT) {
|
|
417
|
+
this._setTerminalState({ state: this._clientClosedTerminalState() });
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
this._setTerminalState({ state: this._protocolTerminalState({ protocol }) });
|
|
421
|
+
}
|
|
422
|
+
if (!this._ready.completed) {
|
|
423
|
+
this._ready.completeError(this._startupException({
|
|
424
|
+
closeKind,
|
|
425
|
+
closeReason: normalizedCloseReason,
|
|
426
|
+
protocol,
|
|
427
|
+
}));
|
|
428
|
+
}
|
|
429
|
+
this._completeRoomClosed();
|
|
430
|
+
}
|
|
431
|
+
_setTerminalState({ state }) {
|
|
432
|
+
if (this._terminalState == null) {
|
|
433
|
+
this._terminalState = state;
|
|
434
|
+
}
|
|
435
|
+
return this._terminalState;
|
|
436
|
+
}
|
|
437
|
+
_raiseIfTerminal() {
|
|
438
|
+
if (this._terminalState != null) {
|
|
439
|
+
throw this._terminalState.requestError();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
_raiseIfTerminalForMessages() {
|
|
443
|
+
if (this._terminalState != null) {
|
|
444
|
+
throw this._terminalState.messageSendError();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
_disconnectedError({ baseMessage }) {
|
|
448
|
+
return new RoomServerException(this._formatClosedMessage({
|
|
449
|
+
baseMessage,
|
|
450
|
+
}));
|
|
451
|
+
}
|
|
452
|
+
_messageDisconnectedError({ baseMessage, }) {
|
|
453
|
+
return new RoomServerException(this._formatClosedMessage({
|
|
454
|
+
baseMessage,
|
|
455
|
+
}));
|
|
456
|
+
}
|
|
457
|
+
_startupException({ closeKind, closeReason, protocol, }) {
|
|
458
|
+
const baseMessage = closeKind === ProtocolCloseKind.ERROR
|
|
459
|
+
? "room connection unexpectedly closed before the room became ready"
|
|
460
|
+
: closeKind === ProtocolCloseKind.CLIENT
|
|
461
|
+
? "room client was closed before the room became ready"
|
|
462
|
+
: "room connection closed before the room became ready";
|
|
463
|
+
return new RoomServerException(this._formatClosedMessage({
|
|
464
|
+
baseMessage,
|
|
465
|
+
protocol,
|
|
466
|
+
closeReason,
|
|
467
|
+
}));
|
|
468
|
+
}
|
|
469
|
+
_finalizeInitialStartupRetryFailure({ retryResult, }) {
|
|
470
|
+
const closeKind = retryResult.closeKind ?? null;
|
|
471
|
+
if (closeKind == null) {
|
|
472
|
+
throw new Error("initial startup retry failure requires a close kind");
|
|
473
|
+
}
|
|
474
|
+
this._setStartupTerminalState({
|
|
475
|
+
closeKind,
|
|
476
|
+
closeReason: retryResult.closeReason ?? null,
|
|
477
|
+
protocol: this._protocolInstance,
|
|
478
|
+
});
|
|
479
|
+
throw this._startupException({
|
|
480
|
+
closeKind,
|
|
481
|
+
closeReason: retryResult.closeReason ?? null,
|
|
482
|
+
protocol: this._protocolInstance,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
_coerceMessageSendError(error) {
|
|
486
|
+
if (this._terminalState == null) {
|
|
487
|
+
return error;
|
|
488
|
+
}
|
|
489
|
+
if (error.message === this._terminalState.requestMessage ||
|
|
490
|
+
error.message === this._terminalState.toolCallMessage) {
|
|
491
|
+
return this._terminalState.messageSendError();
|
|
492
|
+
}
|
|
493
|
+
return error;
|
|
494
|
+
}
|
|
495
|
+
_messageStopError() {
|
|
496
|
+
if (this._closing && this._terminalState != null) {
|
|
497
|
+
return this._terminalState.messageSendError();
|
|
498
|
+
}
|
|
499
|
+
return new RoomServerException("Cannot send messages because messaging has been stopped");
|
|
500
|
+
}
|
|
501
|
+
_failPendingRequests(error) {
|
|
502
|
+
if (this._pendingRequests.size === 0) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const pending = [...this._pendingRequests.values()];
|
|
506
|
+
this._pendingRequests.clear();
|
|
507
|
+
for (const request of pending) {
|
|
508
|
+
if (!request.completed) {
|
|
509
|
+
request.completeError(error);
|
|
510
|
+
}
|
|
63
511
|
}
|
|
512
|
+
}
|
|
513
|
+
async _failToolCallStreams({ error, }) {
|
|
514
|
+
if (this._toolCallStreams.size === 0) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const streams = [...this._toolCallStreams.values()];
|
|
64
518
|
this._toolCallStreams.clear();
|
|
65
|
-
|
|
519
|
+
for (const stream of streams) {
|
|
520
|
+
stream.add(new ErrorContent({ text: error.message }));
|
|
521
|
+
stream.close();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
async _failPendingWork({ state, }) {
|
|
525
|
+
this._failPendingRequests(state.requestError());
|
|
526
|
+
await this._failToolCallStreams({ error: state.toolCallError() });
|
|
527
|
+
}
|
|
528
|
+
async _openProtocol({ initial }) {
|
|
529
|
+
const protocol = this._protocolInstance;
|
|
530
|
+
this._connectionReady = new Completer();
|
|
531
|
+
this._localParticipantReady = new Completer();
|
|
532
|
+
protocol.start({
|
|
533
|
+
onDone: () => {
|
|
534
|
+
const error = roomClosedBeforeReadyError(protocol);
|
|
535
|
+
if (!this._connectionReady.completed) {
|
|
536
|
+
this._connectionReady.completeError(error);
|
|
537
|
+
}
|
|
538
|
+
if (!this._localParticipantReady.completed) {
|
|
539
|
+
this._localParticipantReady.completeError(error);
|
|
540
|
+
}
|
|
541
|
+
if (!initial && !this._ready.completed) {
|
|
542
|
+
this._ready.completeError(error);
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
onError: (error) => {
|
|
546
|
+
const wrapped = wrapRoomConnectionError(error);
|
|
547
|
+
if (!this._connectionReady.completed) {
|
|
548
|
+
this._connectionReady.completeError(wrapped);
|
|
549
|
+
}
|
|
550
|
+
if (!this._localParticipantReady.completed) {
|
|
551
|
+
this._localParticipantReady.completeError(wrapped);
|
|
552
|
+
}
|
|
553
|
+
if (!initial && !this._ready.completed) {
|
|
554
|
+
this._ready.completeError(wrapped);
|
|
555
|
+
}
|
|
556
|
+
},
|
|
557
|
+
});
|
|
558
|
+
try {
|
|
559
|
+
await Promise.all([this._connectionReady.fut, this._localParticipantReady.fut]);
|
|
560
|
+
}
|
|
561
|
+
catch (error) {
|
|
562
|
+
const kind = protocol.closeKind ?? ProtocolCloseKind.ERROR;
|
|
563
|
+
if (!initial && kind !== ProtocolCloseKind.ERROR) {
|
|
564
|
+
throw new ProtocolStartupFailure({
|
|
565
|
+
kind,
|
|
566
|
+
reason: normalizeCloseReason(protocol.closeReason),
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
throw error;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
async start({ onDone, onError, } = {}) {
|
|
573
|
+
if (this._entered) {
|
|
574
|
+
throw new RoomServerException("room client already started");
|
|
575
|
+
}
|
|
576
|
+
this._doneHandler = onDone;
|
|
577
|
+
this._errorHandler = onError;
|
|
578
|
+
try {
|
|
579
|
+
try {
|
|
580
|
+
await this._openProtocol({ initial: true });
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
if (error instanceof ProtocolStartupFailure) {
|
|
584
|
+
if (error.kind !== ProtocolCloseKind.ERROR || this._reconnectTimeout === 0) {
|
|
585
|
+
this._setStartupTerminalState({
|
|
586
|
+
closeKind: error.kind,
|
|
587
|
+
closeReason: error.reason,
|
|
588
|
+
protocol: this._protocolInstance,
|
|
589
|
+
});
|
|
590
|
+
throw this._startupException({
|
|
591
|
+
closeKind: error.kind,
|
|
592
|
+
closeReason: error.reason,
|
|
593
|
+
protocol: this._protocolInstance,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
await this._closeProtocol(this._protocolInstance);
|
|
597
|
+
const retryResult = await this._retryProtocolConnection({
|
|
598
|
+
disconnectReason: error.reason,
|
|
599
|
+
protocolFactoryFailureLogMessage: "unable to create replacement room protocol during initial startup",
|
|
600
|
+
attemptFailureLogMessage: "room startup attempt failed",
|
|
601
|
+
attempt: this._attemptInitialProtocolStartup.bind(this),
|
|
602
|
+
});
|
|
603
|
+
if (!retryResult.connected) {
|
|
604
|
+
this._finalizeInitialStartupRetryFailure({ retryResult });
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
const nonRetryableCloseReason = nonRetryableConnectFailureReason(error);
|
|
609
|
+
if (nonRetryableCloseReason != null) {
|
|
610
|
+
this._finalizeInitialStartupRetryFailure({
|
|
611
|
+
retryResult: {
|
|
612
|
+
connected: false,
|
|
613
|
+
closeKind: ProtocolCloseKind.ERROR,
|
|
614
|
+
closeReason: nonRetryableCloseReason,
|
|
615
|
+
},
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
const closeKind = this._protocolInstance.closeKind;
|
|
619
|
+
const protocolCloseReason = normalizeCloseReason(this._protocolInstance.closeReason);
|
|
620
|
+
if (closeKind != null && closeKind !== ProtocolCloseKind.ERROR) {
|
|
621
|
+
this._setStartupTerminalState({
|
|
622
|
+
closeKind,
|
|
623
|
+
closeReason: protocolCloseReason,
|
|
624
|
+
protocol: this._protocolInstance,
|
|
625
|
+
});
|
|
626
|
+
throw this._startupException({
|
|
627
|
+
closeKind,
|
|
628
|
+
closeReason: protocolCloseReason,
|
|
629
|
+
protocol: this._protocolInstance,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
const closeReason = this._connectionFailureReason(error);
|
|
633
|
+
if (this._reconnectTimeout === 0) {
|
|
634
|
+
this._setStartupTerminalState({
|
|
635
|
+
closeKind: ProtocolCloseKind.ERROR,
|
|
636
|
+
closeReason,
|
|
637
|
+
protocol: this._protocolInstance,
|
|
638
|
+
});
|
|
639
|
+
throw this._startupException({
|
|
640
|
+
closeKind: ProtocolCloseKind.ERROR,
|
|
641
|
+
closeReason,
|
|
642
|
+
protocol: this._protocolInstance,
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
console.debug("room startup attempt failed", error);
|
|
646
|
+
await this._closeProtocol(this._protocolInstance);
|
|
647
|
+
const retryResult = await this._retryProtocolConnection({
|
|
648
|
+
disconnectReason: closeReason,
|
|
649
|
+
protocolFactoryFailureLogMessage: "unable to create replacement room protocol during initial startup",
|
|
650
|
+
attemptFailureLogMessage: "room startup attempt failed",
|
|
651
|
+
attempt: this._attemptInitialProtocolStartup.bind(this),
|
|
652
|
+
});
|
|
653
|
+
if (!retryResult.connected) {
|
|
654
|
+
this._finalizeInitialStartupRetryFailure({ retryResult });
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
this.sync.start();
|
|
659
|
+
this.messaging.start();
|
|
660
|
+
this._entered = true;
|
|
661
|
+
this._markConnected();
|
|
662
|
+
this.messaging._onRoomReconnect();
|
|
663
|
+
this._lifecycleTask = this._connectionLifecycle();
|
|
664
|
+
}
|
|
665
|
+
catch (error) {
|
|
666
|
+
this.sync.dispose();
|
|
667
|
+
void this.messaging.stop();
|
|
668
|
+
this._protocolInstance.dispose();
|
|
669
|
+
throw error;
|
|
670
|
+
}
|
|
671
|
+
await this.ready;
|
|
672
|
+
}
|
|
673
|
+
async _completeReconnect() {
|
|
674
|
+
await this._openProtocol({ initial: false });
|
|
675
|
+
this._allowDisconnectedRequests = true;
|
|
676
|
+
try {
|
|
677
|
+
this._resendLocalAttributesNowait();
|
|
678
|
+
await this.sync._onRoomReconnect();
|
|
679
|
+
this.messaging._onRoomReconnect();
|
|
680
|
+
this._markConnected();
|
|
681
|
+
}
|
|
682
|
+
finally {
|
|
683
|
+
this._allowDisconnectedRequests = false;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
_replaceProtocol(nextProtocol) {
|
|
687
|
+
const currentProtocol = this._protocolInstance;
|
|
688
|
+
this.protocol._unbind(currentProtocol);
|
|
689
|
+
this._protocolInstance = nextProtocol;
|
|
690
|
+
this.protocol._bind(nextProtocol);
|
|
691
|
+
}
|
|
692
|
+
_remainingReconnectTimeout(deadline) {
|
|
693
|
+
if (deadline == null) {
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
const remaining = deadline - Date.now();
|
|
697
|
+
return remaining <= 0 ? 0 : remaining;
|
|
698
|
+
}
|
|
699
|
+
async _attemptInitialProtocolStartup({ protocol, remaining, }) {
|
|
700
|
+
void protocol;
|
|
701
|
+
if (remaining == null) {
|
|
702
|
+
await this._openProtocol({ initial: false });
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
await Promise.race([
|
|
706
|
+
this._openProtocol({ initial: false }),
|
|
707
|
+
new Promise((_resolve, reject) => {
|
|
708
|
+
setTimeout(() => reject(new Error("timeout")), remaining);
|
|
709
|
+
}),
|
|
710
|
+
]);
|
|
711
|
+
}
|
|
712
|
+
async _attemptReconnect({ protocol, remaining, }) {
|
|
713
|
+
try {
|
|
714
|
+
if (remaining == null) {
|
|
715
|
+
await this._completeReconnect();
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
await Promise.race([
|
|
719
|
+
this._completeReconnect(),
|
|
720
|
+
new Promise((_resolve, reject) => {
|
|
721
|
+
setTimeout(() => reject(new Error("timeout")), remaining);
|
|
722
|
+
}),
|
|
723
|
+
]);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
catch (error) {
|
|
727
|
+
if (error instanceof ProtocolStartupFailure) {
|
|
728
|
+
throw error;
|
|
729
|
+
}
|
|
730
|
+
if (error instanceof Error && error.message === "timeout") {
|
|
731
|
+
this._allowDisconnectedRequests = false;
|
|
732
|
+
await this.sync._onRoomDisconnect();
|
|
733
|
+
this.messaging._onRoomDisconnect({ reason: normalizeCloseReason(protocol.closeReason) });
|
|
734
|
+
throw error;
|
|
735
|
+
}
|
|
736
|
+
this._allowDisconnectedRequests = false;
|
|
737
|
+
await this.sync._onRoomDisconnect();
|
|
738
|
+
this.messaging._onRoomDisconnect({ reason: normalizeCloseReason(protocol.closeReason) });
|
|
739
|
+
throw error;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
_formatDuration(milliseconds) {
|
|
743
|
+
if (milliseconds % 1000 === 0) {
|
|
744
|
+
return `${milliseconds / 1000}s`;
|
|
745
|
+
}
|
|
746
|
+
return `${milliseconds / 1000}s`;
|
|
747
|
+
}
|
|
748
|
+
_reconnectTimeoutReason({ disconnectReason, }) {
|
|
749
|
+
if (this._reconnectTimeout == null) {
|
|
750
|
+
throw new Error("reconnect timeout reason requires a configured timeout");
|
|
751
|
+
}
|
|
752
|
+
const timeoutDisplay = this._formatDuration(this._reconnectTimeout);
|
|
753
|
+
if (disconnectReason == null) {
|
|
754
|
+
return `room reconnect timed out after ${timeoutDisplay}`;
|
|
755
|
+
}
|
|
756
|
+
return `room reconnect timed out after ${timeoutDisplay} (${disconnectReason})`;
|
|
757
|
+
}
|
|
758
|
+
_timedOutRetryResult({ disconnectReason, }) {
|
|
759
|
+
if (this._reconnectTimeout == null) {
|
|
760
|
+
throw new Error("timed out retry result requires a configured timeout");
|
|
761
|
+
}
|
|
762
|
+
return {
|
|
763
|
+
connected: false,
|
|
764
|
+
closeKind: ProtocolCloseKind.ERROR,
|
|
765
|
+
closeReason: this._reconnectTimeoutReason({ disconnectReason }),
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
async _closeAfterUnexpectedDisconnect({ closeReason, }) {
|
|
769
|
+
const normalized = normalizeCloseReason(closeReason);
|
|
770
|
+
const state = this._unexpectedCloseTerminalState({ closeReason: normalized });
|
|
771
|
+
this._closeKind = ProtocolCloseKind.ERROR;
|
|
772
|
+
this._closeReason = normalized;
|
|
773
|
+
this._setTerminalState({ state });
|
|
774
|
+
this._completeRoomClosed();
|
|
775
|
+
this._invokeTerminalCallbacks({
|
|
776
|
+
useErrorCallback: true,
|
|
777
|
+
error: state.requestError(),
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
async _closeProtocol(protocol) {
|
|
781
|
+
protocol.dispose();
|
|
782
|
+
await protocol.waitForClose();
|
|
783
|
+
}
|
|
784
|
+
async _retryProtocolConnection({ disconnectReason, protocolFactoryFailureLogMessage, attemptFailureLogMessage, attempt, }) {
|
|
785
|
+
let failureReason = normalizeCloseReason(disconnectReason);
|
|
786
|
+
const recordFailureReason = (reason) => {
|
|
787
|
+
const normalizedReason = normalizeCloseReason(reason);
|
|
788
|
+
if (failureReason == null && normalizedReason != null) {
|
|
789
|
+
failureReason = normalizedReason;
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
const deadline = this._reconnectTimeout == null ? null : Date.now() + this._reconnectTimeout;
|
|
793
|
+
let firstAttempt = true;
|
|
794
|
+
while (!this._closing) {
|
|
795
|
+
if (firstAttempt) {
|
|
796
|
+
firstAttempt = false;
|
|
797
|
+
if (this._reconnectTimeout == null) {
|
|
798
|
+
await new Promise((resolve) => setTimeout(resolve, RoomClient.RECONNECT_RETRY_INTERVAL_MS));
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
const remaining = this._remainingReconnectTimeout(deadline);
|
|
803
|
+
if (remaining != null && remaining === 0) {
|
|
804
|
+
return this._timedOutRetryResult({ disconnectReason: failureReason });
|
|
805
|
+
}
|
|
806
|
+
const delay = remaining == null
|
|
807
|
+
? RoomClient.RECONNECT_RETRY_INTERVAL_MS
|
|
808
|
+
: Math.min(remaining, RoomClient.RECONNECT_RETRY_INTERVAL_MS);
|
|
809
|
+
if (delay > 0) {
|
|
810
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
const remaining = this._remainingReconnectTimeout(deadline);
|
|
814
|
+
if (remaining != null && remaining === 0) {
|
|
815
|
+
return this._timedOutRetryResult({ disconnectReason: failureReason });
|
|
816
|
+
}
|
|
817
|
+
let nextProtocol;
|
|
818
|
+
try {
|
|
819
|
+
nextProtocol = this._protocolFactory();
|
|
820
|
+
}
|
|
821
|
+
catch (error) {
|
|
822
|
+
if (error instanceof ProtocolReconnectUnsupportedException) {
|
|
823
|
+
return {
|
|
824
|
+
connected: false,
|
|
825
|
+
closeKind: ProtocolCloseKind.ERROR,
|
|
826
|
+
closeReason: failureReason,
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
recordFailureReason(String(error));
|
|
830
|
+
console.debug(protocolFactoryFailureLogMessage, error);
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
this._replaceProtocol(nextProtocol);
|
|
834
|
+
try {
|
|
835
|
+
await attempt({ protocol: nextProtocol, remaining });
|
|
836
|
+
}
|
|
837
|
+
catch (error) {
|
|
838
|
+
if (error instanceof Error && error.message === "timeout") {
|
|
839
|
+
recordFailureReason(normalizeCloseReason(nextProtocol.closeReason));
|
|
840
|
+
await this._closeProtocol(nextProtocol);
|
|
841
|
+
return this._timedOutRetryResult({ disconnectReason: failureReason });
|
|
842
|
+
}
|
|
843
|
+
if (error instanceof ProtocolStartupFailure) {
|
|
844
|
+
recordFailureReason(error.reason);
|
|
845
|
+
await this._closeProtocol(nextProtocol);
|
|
846
|
+
if (error.kind !== ProtocolCloseKind.ERROR) {
|
|
847
|
+
return {
|
|
848
|
+
connected: false,
|
|
849
|
+
closeKind: error.kind,
|
|
850
|
+
closeReason: error.reason,
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
const nonRetryableCloseReason = nonRetryableConnectFailureReason(error);
|
|
856
|
+
if (nonRetryableCloseReason != null) {
|
|
857
|
+
await this._closeProtocol(nextProtocol);
|
|
858
|
+
return {
|
|
859
|
+
connected: false,
|
|
860
|
+
closeKind: ProtocolCloseKind.ERROR,
|
|
861
|
+
closeReason: nonRetryableCloseReason,
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
recordFailureReason(this._connectionFailureReason(error));
|
|
865
|
+
console.debug(attemptFailureLogMessage, error);
|
|
866
|
+
await this._closeProtocol(nextProtocol);
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
return { connected: true };
|
|
870
|
+
}
|
|
871
|
+
return {
|
|
872
|
+
connected: false,
|
|
873
|
+
closeKind: ProtocolCloseKind.CLIENT,
|
|
874
|
+
closeReason: this.closeReason,
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
async _reconnect({ disconnectReason, }) {
|
|
878
|
+
const retryResult = await this._retryProtocolConnection({
|
|
879
|
+
disconnectReason,
|
|
880
|
+
protocolFactoryFailureLogMessage: "unable to create replacement room protocol",
|
|
881
|
+
attemptFailureLogMessage: "room reconnect attempt failed",
|
|
882
|
+
attempt: this._attemptReconnect.bind(this),
|
|
883
|
+
});
|
|
884
|
+
if (retryResult.connected) {
|
|
885
|
+
this._emitStatus({
|
|
886
|
+
status: "reconnected",
|
|
887
|
+
message: "room connection restored",
|
|
888
|
+
});
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
const closeKind = retryResult.closeKind ?? null;
|
|
892
|
+
if (closeKind === ProtocolCloseKind.ERROR) {
|
|
893
|
+
const closeReason = retryResult.closeReason ?? null;
|
|
894
|
+
if (closeReason != null && closeReason.startsWith("room reconnect timed out after")) {
|
|
895
|
+
console.warn(`${closeReason}; closing room client`);
|
|
896
|
+
}
|
|
897
|
+
await this._closeAfterUnexpectedDisconnect({ closeReason });
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
if (closeKind == null) {
|
|
901
|
+
throw new Error("reconnect failure requires a close kind");
|
|
902
|
+
}
|
|
903
|
+
const state = this._protocolTerminalState({ protocol: this._protocolInstance });
|
|
904
|
+
this._setTerminalState({ state });
|
|
905
|
+
this._closeKind = closeKind;
|
|
906
|
+
this._closeReason = normalizeCloseReason(retryResult.closeReason ?? null);
|
|
907
|
+
this._completeRoomClosed();
|
|
908
|
+
this._invokeTerminalCallbacks({ useErrorCallback: false });
|
|
909
|
+
return false;
|
|
910
|
+
}
|
|
911
|
+
async _connectionLifecycle() {
|
|
912
|
+
while (true) {
|
|
913
|
+
const protocol = this._protocolInstance;
|
|
914
|
+
await protocol.done;
|
|
915
|
+
const closeKind = protocol.closeKind ?? ProtocolCloseKind.ERROR;
|
|
916
|
+
const closeReason = normalizeCloseReason(protocol.closeReason);
|
|
917
|
+
const state = this._protocolTerminalState({ protocol });
|
|
918
|
+
if (this._closing) {
|
|
919
|
+
this._completeRoomClosed();
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
if (closeKind !== ProtocolCloseKind.ERROR) {
|
|
923
|
+
this._setTerminalState({ state });
|
|
924
|
+
}
|
|
925
|
+
this._markDisconnected({ reason: closeReason, kind: closeKind });
|
|
926
|
+
this._emitStatus({
|
|
927
|
+
status: "disconnected",
|
|
928
|
+
message: closeReason ?? "room connection lost",
|
|
929
|
+
});
|
|
930
|
+
await this.sync._onRoomDisconnect();
|
|
931
|
+
this.messaging._onRoomDisconnect({ reason: closeReason });
|
|
932
|
+
await this._failPendingWork({ state });
|
|
933
|
+
await this._closeProtocol(protocol);
|
|
934
|
+
if (closeKind === ProtocolCloseKind.ERROR) {
|
|
935
|
+
if (this._reconnectTimeout === 0) {
|
|
936
|
+
if (closeReason == null) {
|
|
937
|
+
console.warn("room connection lost; automatic reconnect disabled");
|
|
938
|
+
}
|
|
939
|
+
else {
|
|
940
|
+
console.warn(`room connection lost (${closeReason}); automatic reconnect disabled`);
|
|
941
|
+
}
|
|
942
|
+
await this._closeAfterUnexpectedDisconnect({ closeReason });
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
if (closeReason == null) {
|
|
946
|
+
console.warn("room connection lost; automatically attempting to reconnect");
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
console.warn(`room connection lost (${closeReason}); automatically attempting to reconnect`);
|
|
950
|
+
}
|
|
951
|
+
if (await this._reconnect({ disconnectReason: closeReason })) {
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
this._closeKind = closeKind;
|
|
957
|
+
this._closeReason = closeReason;
|
|
958
|
+
this._completeRoomClosed();
|
|
959
|
+
this._invokeTerminalCallbacks({ useErrorCallback: false });
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
dispose() {
|
|
964
|
+
this._closing = true;
|
|
965
|
+
this._markDisconnected({
|
|
966
|
+
reason: this.closeReason,
|
|
967
|
+
kind: this.closeKind ?? ProtocolCloseKind.CLIENT,
|
|
968
|
+
});
|
|
969
|
+
const closingState = this._clientClosedTerminalState();
|
|
970
|
+
this._setTerminalState({ state: closingState });
|
|
971
|
+
this._failPendingRequests(closingState.requestError());
|
|
972
|
+
void this._failToolCallStreams({ error: closingState.toolCallError() });
|
|
973
|
+
this.sync.dispose();
|
|
974
|
+
void this.messaging.stop();
|
|
975
|
+
this._protocolInstance.dispose();
|
|
976
|
+
this._entered = false;
|
|
977
|
+
this._closeKind = ProtocolCloseKind.CLIENT;
|
|
978
|
+
this._completeRoomClosed();
|
|
979
|
+
this._invokeTerminalCallbacks({ useErrorCallback: false });
|
|
66
980
|
this._localParticipant = null;
|
|
67
981
|
}
|
|
982
|
+
_sendProtocolNowait({ type, data, label, messageId, expectResponse = false, }) {
|
|
983
|
+
try {
|
|
984
|
+
this._raiseIfTerminal();
|
|
985
|
+
}
|
|
986
|
+
catch (error) {
|
|
987
|
+
console.debug(`skipping ${label} because the room is closed`, error);
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
if (this._entered && !this._connected && !this._allowDisconnectedRequests) {
|
|
991
|
+
console.debug(`skipping ${label} while room is disconnected`);
|
|
992
|
+
return null;
|
|
993
|
+
}
|
|
994
|
+
const protocol = this._protocolInstance;
|
|
995
|
+
const resolvedMessageId = messageId ?? protocol.getNextMessageId();
|
|
996
|
+
if (expectResponse) {
|
|
997
|
+
this._ignoredResponseLabels.set(resolvedMessageId, label);
|
|
998
|
+
}
|
|
999
|
+
try {
|
|
1000
|
+
protocol.sendNowait(type, data, { id: resolvedMessageId });
|
|
1001
|
+
}
|
|
1002
|
+
catch (error) {
|
|
1003
|
+
this._ignoredResponseLabels.delete(resolvedMessageId);
|
|
1004
|
+
if (this.isClosed) {
|
|
1005
|
+
console.debug(`skipping ${label} because the room is closed`, error);
|
|
1006
|
+
}
|
|
1007
|
+
else {
|
|
1008
|
+
console.warn(`unable to queue ${label}`, error);
|
|
1009
|
+
}
|
|
1010
|
+
return null;
|
|
1011
|
+
}
|
|
1012
|
+
return resolvedMessageId;
|
|
1013
|
+
}
|
|
1014
|
+
_sendRoomRequestNowait(type, request, { data, label, expectResponse = false, }) {
|
|
1015
|
+
return this._sendProtocolNowait({
|
|
1016
|
+
type,
|
|
1017
|
+
data: packMessage(request, data),
|
|
1018
|
+
label,
|
|
1019
|
+
expectResponse,
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
invokeNowait({ toolkit, tool, input, participantId, onBehalfOfId, callerContext, }) {
|
|
1023
|
+
const resolvedInput = input ?? new EmptyContent();
|
|
1024
|
+
const packedInput = unpackMessage(resolvedInput.pack());
|
|
1025
|
+
const request = {
|
|
1026
|
+
toolkit,
|
|
1027
|
+
tool,
|
|
1028
|
+
participant_id: participantId,
|
|
1029
|
+
on_behalf_of_id: onBehalfOfId,
|
|
1030
|
+
caller_context: callerContext,
|
|
1031
|
+
tool_call_id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
1032
|
+
arguments: packedInput[0],
|
|
1033
|
+
};
|
|
1034
|
+
this._sendRoomRequestNowait("room.invoke_tool", request, {
|
|
1035
|
+
data: packedInput[1].length > 0 ? packedInput[1] : undefined,
|
|
1036
|
+
label: `${toolkit}.${tool}`,
|
|
1037
|
+
expectResponse: true,
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
_sendLocalAttributesNowait(attributes) {
|
|
1041
|
+
this._sendProtocolNowait({
|
|
1042
|
+
type: "set_attributes",
|
|
1043
|
+
data: packMessage(attributes),
|
|
1044
|
+
label: "local participant attribute update",
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
_resendLocalAttributesNowait() {
|
|
1048
|
+
const localParticipant = this._localParticipant;
|
|
1049
|
+
if (localParticipant == null) {
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
const attributes = localParticipant._attributesSnapshot();
|
|
1053
|
+
if (Object.keys(attributes).length === 0) {
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
this._sendLocalAttributesNowait(attributes);
|
|
1057
|
+
}
|
|
68
1058
|
async sendRequest(type, request, data) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
1059
|
+
this._raiseIfTerminal();
|
|
1060
|
+
if (this._entered && !this._connected && !this._allowDisconnectedRequests) {
|
|
1061
|
+
throw this._disconnectedError({ baseMessage: "room connection is disconnected" });
|
|
1062
|
+
}
|
|
1063
|
+
const requestId = this._protocolInstance.getNextMessageId();
|
|
1064
|
+
const completer = new Completer();
|
|
1065
|
+
this._pendingRequests.set(requestId, completer);
|
|
1066
|
+
try {
|
|
1067
|
+
await this._protocolInstance.send(type, packMessage(request, data), requestId);
|
|
1068
|
+
return await completer.fut;
|
|
1069
|
+
}
|
|
1070
|
+
catch (error) {
|
|
1071
|
+
this._pendingRequests.delete(requestId);
|
|
1072
|
+
throw error;
|
|
1073
|
+
}
|
|
75
1074
|
}
|
|
76
1075
|
async call(params) {
|
|
77
1076
|
await this.sendRequest("room.call", params);
|
|
@@ -146,7 +1145,7 @@ export class RoomClient {
|
|
|
146
1145
|
return await this.sendRequest("room.invoke_tool", request, requestData);
|
|
147
1146
|
}
|
|
148
1147
|
async invokeWithStreamInput(params) {
|
|
149
|
-
const toolCallId = `${Date.now()}-${
|
|
1148
|
+
const toolCallId = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
150
1149
|
const request = {
|
|
151
1150
|
toolkit: params.toolkit,
|
|
152
1151
|
tool: params.tool,
|
|
@@ -177,7 +1176,7 @@ export class RoomClient {
|
|
|
177
1176
|
}
|
|
178
1177
|
}
|
|
179
1178
|
async invokeStream(params) {
|
|
180
|
-
const toolCallId = `${Date.now()}-${
|
|
1179
|
+
const toolCallId = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
181
1180
|
const controller = new StreamController();
|
|
182
1181
|
const responseIterator = controller.stream[Symbol.asyncIterator]();
|
|
183
1182
|
this._toolCallStreams.set(toolCallId, controller);
|
|
@@ -199,7 +1198,7 @@ export class RoomClient {
|
|
|
199
1198
|
const requestTask = this._streamInvokeToolRequestChunks(toolCallId, params.input);
|
|
200
1199
|
void requestTask.catch((error) => {
|
|
201
1200
|
const stream = this._toolCallStreams.get(toolCallId);
|
|
202
|
-
if (
|
|
1201
|
+
if (stream == null) {
|
|
203
1202
|
return;
|
|
204
1203
|
}
|
|
205
1204
|
stream.add(new ErrorContent({ text: `request stream failed: ${String(error)}` }));
|
|
@@ -249,8 +1248,8 @@ export class RoomClient {
|
|
|
249
1248
|
}
|
|
250
1249
|
return new JsonContent({ json: { chunk } });
|
|
251
1250
|
}
|
|
252
|
-
async _handleToolCallResponseChunk(protocol,
|
|
253
|
-
if (!
|
|
1251
|
+
async _handleToolCallResponseChunk(protocol, _messageId, _type, data) {
|
|
1252
|
+
if (!this.isActiveProtocol(protocol)) {
|
|
254
1253
|
return;
|
|
255
1254
|
}
|
|
256
1255
|
const [header, payload] = unpackMessage(data);
|
|
@@ -259,7 +1258,7 @@ export class RoomClient {
|
|
|
259
1258
|
return;
|
|
260
1259
|
}
|
|
261
1260
|
const stream = this._toolCallStreams.get(toolCallId);
|
|
262
|
-
if (
|
|
1261
|
+
if (stream == null) {
|
|
263
1262
|
return;
|
|
264
1263
|
}
|
|
265
1264
|
const content = this._decodeToolCallContent({ header, payload });
|
|
@@ -269,51 +1268,91 @@ export class RoomClient {
|
|
|
269
1268
|
this._toolCallStreams.delete(toolCallId);
|
|
270
1269
|
}
|
|
271
1270
|
}
|
|
272
|
-
async _handleResponse(protocol, messageId,
|
|
273
|
-
if (!
|
|
274
|
-
console.error("No data in response");
|
|
1271
|
+
async _handleResponse(protocol, messageId, _type, data) {
|
|
1272
|
+
if (!this.isActiveProtocol(protocol)) {
|
|
275
1273
|
return;
|
|
276
1274
|
}
|
|
277
1275
|
const response = unpackContent(data);
|
|
278
|
-
|
|
279
|
-
if (
|
|
280
|
-
console.error("No response");
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
if (this._pendingRequests.has(messageId)) {
|
|
284
|
-
const pr = this._pendingRequests.get(messageId);
|
|
1276
|
+
const pending = this._pendingRequests.get(messageId);
|
|
1277
|
+
if (pending != null) {
|
|
285
1278
|
this._pendingRequests.delete(messageId);
|
|
286
1279
|
if (response instanceof ErrorContent) {
|
|
287
|
-
|
|
1280
|
+
pending.completeError(new RoomServerException(response.text, response.code));
|
|
288
1281
|
}
|
|
289
1282
|
else {
|
|
290
|
-
|
|
1283
|
+
pending.complete(response);
|
|
291
1284
|
}
|
|
1285
|
+
return;
|
|
292
1286
|
}
|
|
293
|
-
|
|
294
|
-
|
|
1287
|
+
const ignoredLabel = this._ignoredResponseLabels.get(messageId);
|
|
1288
|
+
if (ignoredLabel != null) {
|
|
1289
|
+
this._ignoredResponseLabels.delete(messageId);
|
|
1290
|
+
if (response instanceof ErrorContent) {
|
|
1291
|
+
console.warn(`one-way room request failed for ${ignoredLabel}: ${response.text}`);
|
|
1292
|
+
}
|
|
295
1293
|
}
|
|
296
1294
|
}
|
|
297
|
-
async
|
|
298
|
-
|
|
299
|
-
|
|
1295
|
+
async _handleRoomStatus(protocol, _messageId, _type, data) {
|
|
1296
|
+
if (!this.isActiveProtocol(protocol)) {
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
const [payload] = unpackMessage(data);
|
|
1300
|
+
this.emit(RoomStatusEvent.fromJson(payload));
|
|
1301
|
+
}
|
|
1302
|
+
async _handleRoomReady(protocol, _messageId, _type, data) {
|
|
1303
|
+
if (!this.isActiveProtocol(protocol)) {
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
const [message] = unpackMessage(data);
|
|
1307
|
+
this._roomName = typeof message["room_name"] === "string" ? message["room_name"] : null;
|
|
1308
|
+
this._roomUrl = typeof message["room_url"] === "string" ? message["room_url"] : null;
|
|
1309
|
+
this._sessionId = typeof message["session_id"] === "string" ? message["session_id"] : null;
|
|
1310
|
+
if (!this._ready.completed) {
|
|
1311
|
+
this._ready.complete();
|
|
1312
|
+
}
|
|
1313
|
+
if (!this._connectionReady.completed) {
|
|
1314
|
+
this._connectionReady.complete();
|
|
1315
|
+
}
|
|
300
1316
|
}
|
|
301
1317
|
_onParticipantInit(participantId, attributes) {
|
|
302
|
-
this._localParticipant
|
|
303
|
-
|
|
304
|
-
this._localParticipant.
|
|
1318
|
+
if (this._localParticipant == null) {
|
|
1319
|
+
this._localParticipant = new LocalParticipant(this, participantId);
|
|
1320
|
+
this._localParticipant._setAttributes(attributes);
|
|
1321
|
+
}
|
|
1322
|
+
else {
|
|
1323
|
+
const merged = { ...attributes, ...this._localParticipant._attributesSnapshot() };
|
|
1324
|
+
this._localParticipant._replaceIdentity({
|
|
1325
|
+
participantId,
|
|
1326
|
+
attributes: merged,
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
if (!this._localParticipantReady.completed) {
|
|
1330
|
+
this._localParticipantReady.complete();
|
|
305
1331
|
}
|
|
306
1332
|
}
|
|
307
|
-
async _handleParticipant(protocol,
|
|
308
|
-
|
|
1333
|
+
async _handleParticipant(protocol, _messageId, _type, data) {
|
|
1334
|
+
if (!this.isActiveProtocol(protocol)) {
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
const [message] = unpackMessage(data);
|
|
309
1338
|
switch (message["type"]) {
|
|
310
|
-
case "init":
|
|
1339
|
+
case "init": {
|
|
1340
|
+
const participantId = message["participantId"];
|
|
1341
|
+
const attributes = message["attributes"];
|
|
1342
|
+
if (typeof participantId === "string" &&
|
|
1343
|
+
typeof attributes === "object" &&
|
|
1344
|
+
attributes !== null &&
|
|
1345
|
+
!Array.isArray(attributes)) {
|
|
1346
|
+
this._onParticipantInit(participantId, attributes);
|
|
1347
|
+
}
|
|
1348
|
+
break;
|
|
1349
|
+
}
|
|
1350
|
+
default:
|
|
1351
|
+
break;
|
|
311
1352
|
}
|
|
312
1353
|
}
|
|
313
|
-
|
|
314
|
-
this.
|
|
315
|
-
}
|
|
316
|
-
listen() {
|
|
317
|
-
return this._eventsController.stream;
|
|
1354
|
+
_emitStatus({ status, message, }) {
|
|
1355
|
+
this.emit(new RoomStatusEvent({ status, message }));
|
|
318
1356
|
}
|
|
319
1357
|
}
|
|
1358
|
+
RoomClient.RECONNECT_RETRY_INTERVAL_MS = 1000;
|