@reactor-team/js-sdk 1.0.6 → 1.0.7
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/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +43 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +43 -39
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -1
- package/.env.example +0 -2
- package/.prettierignore +0 -7
- package/.prettierrc +0 -9
- package/publish_package.sh +0 -45
- package/src/core/CoordinatorClient.ts +0 -167
- package/src/core/GPUMachineClient.ts +0 -172
- package/src/core/Reactor.ts +0 -406
- package/src/core/store.ts +0 -163
- package/src/core/types.ts +0 -99
- package/src/index.ts +0 -5
- package/src/react/ReactorProvider.tsx +0 -86
- package/src/react/ReactorView.tsx +0 -116
- package/src/react/hooks.ts +0 -50
- package/src/types.ts +0 -37
- package/tsconfig.json +0 -19
- package/tsup.config.ts +0 -15
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The GPUMachineClient is responsible for handling the direct connection to the machine instance
|
|
3
|
-
* after the coordinator has assigned a machine.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
type GPUMachineSendMessage,
|
|
8
|
-
GPUMachineReceiveMessageSchema,
|
|
9
|
-
} from "./types";
|
|
10
|
-
import { Room, RoomEvent, Track, RemoteVideoTrack } from "livekit-client";
|
|
11
|
-
|
|
12
|
-
type EventHandler = (...args: any[]) => void;
|
|
13
|
-
export type GPUMachineEvent =
|
|
14
|
-
| GPUMachineReceiveMessageSchema["type"]
|
|
15
|
-
| "statusChanged"
|
|
16
|
-
| "streamChanged";
|
|
17
|
-
|
|
18
|
-
export class GPUMachineClient {
|
|
19
|
-
private eventListeners: Map<GPUMachineEvent, Set<EventHandler>> = new Map();
|
|
20
|
-
private roomInstance: Room | undefined;
|
|
21
|
-
private machineFPS: number | undefined;
|
|
22
|
-
private token: string;
|
|
23
|
-
private videoStream: MediaStream | undefined;
|
|
24
|
-
private liveKitUrl: string;
|
|
25
|
-
|
|
26
|
-
constructor(token: string, liveKitUrl: string) {
|
|
27
|
-
this.token = token;
|
|
28
|
-
this.liveKitUrl = liveKitUrl;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Event Emitter API
|
|
32
|
-
on(event: GPUMachineEvent, handler: EventHandler) {
|
|
33
|
-
if (!this.eventListeners.has(event)) {
|
|
34
|
-
this.eventListeners.set(event, new Set());
|
|
35
|
-
}
|
|
36
|
-
this.eventListeners.get(event)!.add(handler);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
off(event: GPUMachineEvent, handler: EventHandler) {
|
|
40
|
-
this.eventListeners.get(event)?.delete(handler);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
emit(event: GPUMachineEvent, ...args: any[]) {
|
|
44
|
-
this.eventListeners.get(event)?.forEach((handler) => handler(...args));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
sendMessage(message: GPUMachineSendMessage) {
|
|
48
|
-
try {
|
|
49
|
-
if (this.roomInstance) {
|
|
50
|
-
const messageStr = JSON.stringify(message);
|
|
51
|
-
// Send message via LiveKit text channel
|
|
52
|
-
this.roomInstance.localParticipant.sendText(messageStr, {
|
|
53
|
-
topic: "application",
|
|
54
|
-
});
|
|
55
|
-
} else {
|
|
56
|
-
console.warn(
|
|
57
|
-
"[GPUMachineClient] Cannot send message - not connected to room"
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
} catch (error) {
|
|
61
|
-
console.error("[GPUMachineClient] Failed to send message:", error);
|
|
62
|
-
this.emit("statusChanged", "error");
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async connect(): Promise<void> {
|
|
67
|
-
this.roomInstance = new Room({
|
|
68
|
-
adaptiveStream: true,
|
|
69
|
-
dynacast: true,
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
this.roomInstance.on(RoomEvent.Connected, () => {
|
|
73
|
-
console.debug("[GPUMachineClient] Connected to room");
|
|
74
|
-
this.emit("statusChanged", "connected");
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
this.roomInstance.on(RoomEvent.Disconnected, () => {
|
|
78
|
-
console.debug("[GPUMachineClient] Disconnected from room");
|
|
79
|
-
this.emit("statusChanged", "disconnected");
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// Handle video track subscriptions from GPU machine
|
|
83
|
-
this.roomInstance.on(
|
|
84
|
-
RoomEvent.TrackSubscribed,
|
|
85
|
-
(track, _publication, participant) => {
|
|
86
|
-
console.debug(
|
|
87
|
-
"[GPUMachineClient] Track subscribed:",
|
|
88
|
-
track.kind,
|
|
89
|
-
participant.identity
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
if (track.kind === Track.Kind.Video) {
|
|
93
|
-
const videoTrack = track as RemoteVideoTrack;
|
|
94
|
-
this.emit("streamChanged", videoTrack);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
this.roomInstance.on(
|
|
100
|
-
RoomEvent.TrackUnsubscribed,
|
|
101
|
-
(track, _publication, participant) => {
|
|
102
|
-
console.debug(
|
|
103
|
-
"[GPUMachineClient] Track unsubscribed:",
|
|
104
|
-
track.kind,
|
|
105
|
-
participant.identity
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
if (track.kind === Track.Kind.Video) {
|
|
109
|
-
this.emit("streamChanged", null);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
this.roomInstance.registerTextStreamHandler(
|
|
115
|
-
"application",
|
|
116
|
-
async (reader, participant) => {
|
|
117
|
-
const text = await reader.readAll();
|
|
118
|
-
console.log("[GPUMachineClient] Received message:", text);
|
|
119
|
-
try {
|
|
120
|
-
const parsedData = JSON.parse(text);
|
|
121
|
-
|
|
122
|
-
const validatedMessage =
|
|
123
|
-
GPUMachineReceiveMessageSchema.parse(parsedData);
|
|
124
|
-
if (validatedMessage.type === "fps") {
|
|
125
|
-
this.machineFPS = validatedMessage.data;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
this.emit(validatedMessage.type, validatedMessage.data);
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.error(
|
|
131
|
-
"[GPUMachineClient] Failed to parse/validate message:",
|
|
132
|
-
error
|
|
133
|
-
);
|
|
134
|
-
this.emit("statusChanged", "error");
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
await this.roomInstance.connect(this.liveKitUrl, this.token);
|
|
140
|
-
|
|
141
|
-
console.log("[GPUMachineClient] Room connected");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Closes the LiveKit connection if it exists.
|
|
146
|
-
* This will trigger the onclose event handler.
|
|
147
|
-
*/
|
|
148
|
-
async disconnect() {
|
|
149
|
-
if (this.roomInstance) {
|
|
150
|
-
console.debug("[GPUMachineClient] Closing LiveKit connection");
|
|
151
|
-
await this.roomInstance.disconnect();
|
|
152
|
-
this.roomInstance = undefined;
|
|
153
|
-
}
|
|
154
|
-
this.machineFPS = undefined;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Returns the current fps rate of the machine.
|
|
159
|
-
* @returns The current fps rate of the machine.
|
|
160
|
-
*/
|
|
161
|
-
getFPS(): number | undefined {
|
|
162
|
-
return this.machineFPS;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Returns the current video stream from the GPU machine.
|
|
167
|
-
* @returns The current video stream or undefined if not available.
|
|
168
|
-
*/
|
|
169
|
-
getVideoStream(): MediaStream | undefined {
|
|
170
|
-
return this.videoStream;
|
|
171
|
-
}
|
|
172
|
-
}
|
package/src/core/Reactor.ts
DELETED
|
@@ -1,406 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ApplicationMessage,
|
|
3
|
-
InternalConnectionStatus,
|
|
4
|
-
GPUMachineAssignmentData,
|
|
5
|
-
} from "./types";
|
|
6
|
-
import type {
|
|
7
|
-
ReactorEvent,
|
|
8
|
-
ReactorStatus,
|
|
9
|
-
ReactorState,
|
|
10
|
-
ReactorError,
|
|
11
|
-
ReactorWaitingInfo,
|
|
12
|
-
} from "../types";
|
|
13
|
-
import { CoordinatorClient } from "./CoordinatorClient";
|
|
14
|
-
import { GPUMachineClient } from "./GPUMachineClient";
|
|
15
|
-
import { z } from "zod";
|
|
16
|
-
|
|
17
|
-
const OptionsSchema = z
|
|
18
|
-
.object({
|
|
19
|
-
directConnection: z
|
|
20
|
-
.object({
|
|
21
|
-
livekitJwtToken: z.string(),
|
|
22
|
-
livekitWsUrl: z.string(),
|
|
23
|
-
})
|
|
24
|
-
.optional(),
|
|
25
|
-
insecureApiKey: z.string().optional(),
|
|
26
|
-
jwtToken: z.string().optional(),
|
|
27
|
-
coordinatorUrl: z.string().default("wss://api.reactor.inc/ws"),
|
|
28
|
-
modelName: z.string(),
|
|
29
|
-
})
|
|
30
|
-
.refine(
|
|
31
|
-
(data) => data.directConnection || data.insecureApiKey || data.jwtToken,
|
|
32
|
-
{
|
|
33
|
-
message:
|
|
34
|
-
"At least one of directConnection, insecureApiKey, or jwtToken must be provided.",
|
|
35
|
-
}
|
|
36
|
-
);
|
|
37
|
-
export type Options = z.input<typeof OptionsSchema>;
|
|
38
|
-
|
|
39
|
-
type EventHandler = (...args: any[]) => void;
|
|
40
|
-
|
|
41
|
-
export class Reactor {
|
|
42
|
-
private coordinatorClient: CoordinatorClient | undefined; //client for the coordinator
|
|
43
|
-
private machineClient: GPUMachineClient | undefined; //client for the machine instance
|
|
44
|
-
private status: ReactorStatus = "disconnected";
|
|
45
|
-
private coordinatorUrl: string;
|
|
46
|
-
private lastError?: ReactorError;
|
|
47
|
-
private waitingInfo?: ReactorWaitingInfo;
|
|
48
|
-
private jwtToken?: string;
|
|
49
|
-
private insecureApiKey?: string;
|
|
50
|
-
private directConnection?: {
|
|
51
|
-
livekitJwtToken: string;
|
|
52
|
-
livekitWsUrl: string;
|
|
53
|
-
};
|
|
54
|
-
private modelName: string;
|
|
55
|
-
private modelVersion: string;
|
|
56
|
-
|
|
57
|
-
constructor(options: Options) {
|
|
58
|
-
const validatedOptions = OptionsSchema.parse(options);
|
|
59
|
-
this.coordinatorUrl = validatedOptions.coordinatorUrl;
|
|
60
|
-
this.jwtToken = validatedOptions.jwtToken;
|
|
61
|
-
this.insecureApiKey = validatedOptions.insecureApiKey;
|
|
62
|
-
this.directConnection = validatedOptions.directConnection;
|
|
63
|
-
this.modelName = validatedOptions.modelName;
|
|
64
|
-
// TODO: Use the model version from the options once we have a way to handle multiple versions
|
|
65
|
-
this.modelVersion = "1.0.0";
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Generic event map
|
|
69
|
-
private eventListeners: Map<ReactorEvent, Set<EventHandler>> = new Map();
|
|
70
|
-
|
|
71
|
-
// Event Emitter API
|
|
72
|
-
on(event: ReactorEvent, handler: EventHandler) {
|
|
73
|
-
if (!this.eventListeners.has(event)) {
|
|
74
|
-
this.eventListeners.set(event, new Set());
|
|
75
|
-
}
|
|
76
|
-
this.eventListeners.get(event)!.add(handler);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
off(event: ReactorEvent, handler: EventHandler) {
|
|
80
|
-
this.eventListeners.get(event)?.delete(handler);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
emit(event: ReactorEvent, ...args: any[]) {
|
|
84
|
-
this.eventListeners.get(event)?.forEach((handler) => handler(...args));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Public method to send a message to the machine.
|
|
89
|
-
* Automatically wraps the message in an application message.
|
|
90
|
-
* @param message The message to send to the machine.
|
|
91
|
-
* @throws Error if not in ready state
|
|
92
|
-
*/
|
|
93
|
-
sendMessage(message: any) {
|
|
94
|
-
// Synchronous validation - throw immediately
|
|
95
|
-
if (process.env.NODE_ENV !== "development" && this.status !== "ready") {
|
|
96
|
-
const errorMessage = `Cannot send message, status is ${this.status}`;
|
|
97
|
-
console.error("[Reactor] Not ready, cannot send message");
|
|
98
|
-
throw new Error(errorMessage);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
// Automatically wrap the user-message in an application message.
|
|
103
|
-
const applicationMessage: ApplicationMessage = {
|
|
104
|
-
type: "application",
|
|
105
|
-
data: message,
|
|
106
|
-
};
|
|
107
|
-
this.machineClient?.sendMessage(applicationMessage);
|
|
108
|
-
} catch (error) {
|
|
109
|
-
// Async operational error - emit event only
|
|
110
|
-
console.error("[Reactor] Failed to send message:", error);
|
|
111
|
-
this.createError(
|
|
112
|
-
"MESSAGE_SEND_FAILED",
|
|
113
|
-
`Failed to send message: ${error}`,
|
|
114
|
-
"gpu",
|
|
115
|
-
true
|
|
116
|
-
);
|
|
117
|
-
// Don't re-throw - let the error event handle it
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Connects to the machine via LiveKit and waits for the gpu machine to be ready.
|
|
123
|
-
* Once the machine is ready, the Reactor will establish the LiveKit connection.
|
|
124
|
-
* @param livekitJwtToken The JWT token for LiveKit authentication
|
|
125
|
-
* @param livekitWsUrl The WebSocket URL for LiveKit connection
|
|
126
|
-
*/
|
|
127
|
-
private async connectToGPUMachine(
|
|
128
|
-
livekitJwtToken: string,
|
|
129
|
-
livekitWsUrl: string
|
|
130
|
-
) {
|
|
131
|
-
console.debug("[Reactor] Connecting to machine room...");
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
this.machineClient = new GPUMachineClient(livekitJwtToken, livekitWsUrl);
|
|
135
|
-
|
|
136
|
-
this.machineClient.on("application", (message: any) => {
|
|
137
|
-
// escalate the message event through the instance, so that the user can listen to it.
|
|
138
|
-
this.emit("newMessage", message);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
this.machineClient.on(
|
|
142
|
-
"statusChanged",
|
|
143
|
-
(status: InternalConnectionStatus) => {
|
|
144
|
-
switch (status) {
|
|
145
|
-
case "connected":
|
|
146
|
-
this.setStatus("ready");
|
|
147
|
-
break;
|
|
148
|
-
case "disconnected":
|
|
149
|
-
// gpu machine has disconnected. Close any other connection to any other entity
|
|
150
|
-
// such as the coordinator or the LiveKit connection.
|
|
151
|
-
this.disconnect();
|
|
152
|
-
break;
|
|
153
|
-
case "error":
|
|
154
|
-
// gpu machine has errored. Close any other connection to any other entity
|
|
155
|
-
// such as the coordinator or the LiveKit connection.
|
|
156
|
-
this.createError(
|
|
157
|
-
"GPU_CONNECTION_ERROR",
|
|
158
|
-
"GPU machine connection failed",
|
|
159
|
-
"gpu",
|
|
160
|
-
true
|
|
161
|
-
);
|
|
162
|
-
this.disconnect();
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
this.machineClient.on("fps", (fps: number) => {
|
|
169
|
-
this.emit("fps", fps);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
this.machineClient.on("streamChanged", (videoTrack) => {
|
|
173
|
-
this.emit("streamChanged", videoTrack);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
console.debug("[Reactor] About to connect to machine");
|
|
177
|
-
await this.machineClient.connect();
|
|
178
|
-
} catch (error) {
|
|
179
|
-
// For private methods, we can still throw since the caller will handle it appropriately
|
|
180
|
-
throw error;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Connects to the coordinator and waits for a GPU to be assigned.
|
|
186
|
-
* Once a GPU is assigned, the Reactor will connect to the gpu machine via LiveKit.
|
|
187
|
-
*/
|
|
188
|
-
async connect(): Promise<void> {
|
|
189
|
-
console.debug("[Reactor] Connecting, status:", this.status);
|
|
190
|
-
|
|
191
|
-
// Synchronous validation - throw immediately
|
|
192
|
-
if (this.status !== "disconnected")
|
|
193
|
-
throw new Error("Already connected or connecting");
|
|
194
|
-
|
|
195
|
-
if (this.directConnection) {
|
|
196
|
-
return this.connectToGPUMachine(
|
|
197
|
-
this.directConnection.livekitJwtToken,
|
|
198
|
-
this.directConnection.livekitWsUrl
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
this.setStatus("connecting");
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
console.debug(
|
|
206
|
-
"[Reactor] Connecting to coordinator with authenticated URL"
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
this.coordinatorClient = new CoordinatorClient({
|
|
210
|
-
wsUrl: this.coordinatorUrl,
|
|
211
|
-
jwtToken: this.jwtToken,
|
|
212
|
-
insecureApiKey: this.insecureApiKey,
|
|
213
|
-
modelName: this.modelName,
|
|
214
|
-
modelVersion: this.modelVersion,
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
this.coordinatorClient.on(
|
|
218
|
-
"gpu-machine-assigned",
|
|
219
|
-
async (assignmentData: GPUMachineAssignmentData) => {
|
|
220
|
-
console.debug(
|
|
221
|
-
"[Reactor] GPU machine assigned by coordinator:",
|
|
222
|
-
assignmentData
|
|
223
|
-
);
|
|
224
|
-
try {
|
|
225
|
-
await this.connectToGPUMachine(
|
|
226
|
-
assignmentData.livekitJwtToken,
|
|
227
|
-
assignmentData.livekitWsUrl
|
|
228
|
-
);
|
|
229
|
-
} catch (error) {
|
|
230
|
-
console.error("[Reactor] Failed to connect to GPU machine:", error);
|
|
231
|
-
// Emit error event for async GPU connection failure
|
|
232
|
-
this.createError(
|
|
233
|
-
"GPU_CONNECTION_FAILED",
|
|
234
|
-
`Failed to connect to GPU machine: ${error}`,
|
|
235
|
-
"gpu",
|
|
236
|
-
true
|
|
237
|
-
);
|
|
238
|
-
this.disconnect();
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
);
|
|
242
|
-
|
|
243
|
-
this.coordinatorClient.on(
|
|
244
|
-
"waiting-info",
|
|
245
|
-
(waitingData: ReactorWaitingInfo) => {
|
|
246
|
-
console.debug("[Reactor] Waiting info update received:", waitingData);
|
|
247
|
-
// Update waiting info
|
|
248
|
-
this.setWaitingInfo({
|
|
249
|
-
...this.waitingInfo,
|
|
250
|
-
...waitingData,
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
this.coordinatorClient.on(
|
|
256
|
-
"statusChanged",
|
|
257
|
-
(newStatus: InternalConnectionStatus) => {
|
|
258
|
-
switch (newStatus) {
|
|
259
|
-
case "connected":
|
|
260
|
-
this.setStatus("waiting");
|
|
261
|
-
// Initialize waiting info when entering waiting state
|
|
262
|
-
this.setWaitingInfo({
|
|
263
|
-
position: undefined,
|
|
264
|
-
estimatedWaitTime: undefined,
|
|
265
|
-
averageWaitTime: undefined,
|
|
266
|
-
});
|
|
267
|
-
break;
|
|
268
|
-
case "disconnected":
|
|
269
|
-
this.disconnect();
|
|
270
|
-
break;
|
|
271
|
-
case "error":
|
|
272
|
-
this.createError(
|
|
273
|
-
"COORDINATOR_CONNECTION_ERROR",
|
|
274
|
-
"Coordinator connection failed",
|
|
275
|
-
"coordinator",
|
|
276
|
-
true
|
|
277
|
-
);
|
|
278
|
-
this.disconnect();
|
|
279
|
-
break;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
await this.coordinatorClient.connect();
|
|
285
|
-
} catch (error) {
|
|
286
|
-
console.error("[Reactor] Authentication failed:", error);
|
|
287
|
-
this.createError(
|
|
288
|
-
"AUTHENTICATION_FAILED",
|
|
289
|
-
`Authentication failed: ${error}`,
|
|
290
|
-
"coordinator",
|
|
291
|
-
true
|
|
292
|
-
);
|
|
293
|
-
this.setStatus("disconnected");
|
|
294
|
-
// Keep throwing for authentication failures as these are immediate/sync failures
|
|
295
|
-
throw error;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Disconnects from the coordinator and the gpu machine.
|
|
301
|
-
* Ensures cleanup completes even if individual disconnections fail.
|
|
302
|
-
*/
|
|
303
|
-
async disconnect() {
|
|
304
|
-
if (this.status === "disconnected") return;
|
|
305
|
-
|
|
306
|
-
// Disconnect coordinator client with error handling
|
|
307
|
-
if (this.coordinatorClient) {
|
|
308
|
-
try {
|
|
309
|
-
this.coordinatorClient.disconnect();
|
|
310
|
-
} catch (error) {
|
|
311
|
-
console.error("[Reactor] Error disconnecting from coordinator:", error);
|
|
312
|
-
// Continue with cleanup even if coordinator disconnect fails
|
|
313
|
-
}
|
|
314
|
-
this.coordinatorClient = undefined;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Disconnect machine client with error handling
|
|
318
|
-
if (this.machineClient) {
|
|
319
|
-
try {
|
|
320
|
-
await this.machineClient.disconnect();
|
|
321
|
-
} catch (error) {
|
|
322
|
-
console.error("[Reactor] Error disconnecting from GPU machine:", error);
|
|
323
|
-
// Continue with cleanup even if machine disconnect fails
|
|
324
|
-
}
|
|
325
|
-
this.machineClient = undefined;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
this.setStatus("disconnected");
|
|
329
|
-
|
|
330
|
-
// Clear waiting info when disconnecting
|
|
331
|
-
this.waitingInfo = undefined;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
private setStatus(newStatus: ReactorStatus) {
|
|
335
|
-
console.debug("[Reactor] Setting status:", newStatus, "from", this.status);
|
|
336
|
-
if (this.status !== newStatus) {
|
|
337
|
-
this.status = newStatus;
|
|
338
|
-
this.emit("statusChanged", newStatus);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
private setWaitingInfo(newWaitingInfo: ReactorWaitingInfo) {
|
|
343
|
-
console.debug(
|
|
344
|
-
"[Reactor] Setting waiting info:",
|
|
345
|
-
newWaitingInfo,
|
|
346
|
-
"from",
|
|
347
|
-
this.waitingInfo
|
|
348
|
-
);
|
|
349
|
-
if (this.waitingInfo !== newWaitingInfo) {
|
|
350
|
-
this.waitingInfo = newWaitingInfo;
|
|
351
|
-
this.emit("waitingInfoChanged", newWaitingInfo);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
getStatus(): ReactorStatus {
|
|
356
|
-
return this.status;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Get the current state including status, error, and waiting info
|
|
361
|
-
*/
|
|
362
|
-
getState(): ReactorState {
|
|
363
|
-
return {
|
|
364
|
-
status: this.status,
|
|
365
|
-
waitingInfo: this.waitingInfo,
|
|
366
|
-
lastError: this.lastError,
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Get waiting information when status is 'waiting'
|
|
372
|
-
*/
|
|
373
|
-
getWaitingInfo(): ReactorWaitingInfo | undefined {
|
|
374
|
-
return this.waitingInfo;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Get the last error that occurred
|
|
379
|
-
*/
|
|
380
|
-
getLastError(): ReactorError | undefined {
|
|
381
|
-
return this.lastError;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Create and store an error
|
|
386
|
-
*/
|
|
387
|
-
private createError(
|
|
388
|
-
code: string,
|
|
389
|
-
message: string,
|
|
390
|
-
component: "coordinator" | "gpu" | "livekit",
|
|
391
|
-
recoverable: boolean,
|
|
392
|
-
retryAfter?: number
|
|
393
|
-
) {
|
|
394
|
-
this.lastError = {
|
|
395
|
-
code,
|
|
396
|
-
message,
|
|
397
|
-
timestamp: Date.now(),
|
|
398
|
-
recoverable,
|
|
399
|
-
component,
|
|
400
|
-
retryAfter,
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
// Emit error event
|
|
404
|
-
this.emit("error", this.lastError);
|
|
405
|
-
}
|
|
406
|
-
}
|