@hardlydifficult/worker-server 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/README.md +193 -287
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,398 +13,304 @@ npm install @hardlydifficult/worker-server
|
|
|
13
13
|
```typescript
|
|
14
14
|
import { WorkerServer } from "@hardlydifficult/worker-server";
|
|
15
15
|
|
|
16
|
-
const server = new WorkerServer({
|
|
17
|
-
port: 8080,
|
|
18
|
-
authToken: "my-secret-token", // optional
|
|
19
|
-
logger: console, // optional, defaults to no-op
|
|
20
|
-
});
|
|
16
|
+
const server = new WorkerServer({ port: 8080 });
|
|
21
17
|
|
|
22
|
-
// Handle worker registrations and messages
|
|
23
18
|
server.onWorkerConnected((worker) => {
|
|
24
|
-
console.log(`Worker ${worker.name} (${worker.id})
|
|
19
|
+
console.log(`Worker ${worker.name} connected (${worker.id})`);
|
|
25
20
|
});
|
|
26
21
|
|
|
27
|
-
server.onWorkerDisconnected((worker,
|
|
28
|
-
console.log(`Worker ${worker.
|
|
22
|
+
server.onWorkerDisconnected((worker, pending) => {
|
|
23
|
+
console.log(`Worker ${worker.name} disconnected with ${pending.size} pending requests`);
|
|
29
24
|
});
|
|
30
25
|
|
|
31
26
|
server.onWorkerMessage("work_complete", (worker, message) => {
|
|
32
|
-
console.log(`
|
|
27
|
+
console.log(`Worker ${worker.id} completed request ${message.requestId}`);
|
|
33
28
|
});
|
|
34
29
|
|
|
35
|
-
// Start the server
|
|
36
30
|
await server.start();
|
|
37
|
-
|
|
38
|
-
// Get an available worker supporting a model
|
|
39
|
-
const worker = server.getAvailableWorker("gpt-4");
|
|
40
|
-
if (worker) {
|
|
41
|
-
server.send(worker.id, { type: "work_request", requestId: "req-1" });
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Stop when done
|
|
45
|
-
await server.stop();
|
|
31
|
+
console.log("Server listening on port 8080");
|
|
46
32
|
```
|
|
47
33
|
|
|
48
|
-
## Core
|
|
49
|
-
|
|
50
|
-
### Worker Lifecycle Management
|
|
34
|
+
## Core Components
|
|
51
35
|
|
|
52
|
-
|
|
36
|
+
### WorkerServer
|
|
53
37
|
|
|
54
|
-
|
|
38
|
+
WebSocket server managing remote worker connections with health checks, message routing, and pool management.
|
|
55
39
|
|
|
56
|
-
|
|
40
|
+
#### Lifecycle Management
|
|
57
41
|
|
|
58
42
|
```typescript
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const server = new WorkerServer({
|
|
62
|
-
port: 8080,
|
|
63
|
-
authToken: "secret",
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
server.onWorkerConnected((worker) => {
|
|
67
|
-
console.log(`Worker registered: ${worker.id}`);
|
|
68
|
-
});
|
|
43
|
+
const server = new WorkerServer({ port: 8080, authToken: "secret" });
|
|
69
44
|
|
|
45
|
+
// Start the server
|
|
70
46
|
await server.start();
|
|
71
|
-
```
|
|
72
47
|
|
|
73
|
-
|
|
48
|
+
// Stop the server gracefully
|
|
49
|
+
await server.stop();
|
|
50
|
+
```
|
|
74
51
|
|
|
75
|
-
|
|
52
|
+
#### Registration Handlers
|
|
76
53
|
|
|
77
54
|
```typescript
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
healthCheckIntervalMs: 10_000, // Check every 10s
|
|
82
|
-
heartbeatIntervalMs: 15_000, // Communicate 15s interval to workers
|
|
55
|
+
// Called when a worker successfully registers
|
|
56
|
+
const unsubscribeConnected = server.onWorkerConnected((worker) => {
|
|
57
|
+
console.log(`Worker connected: ${worker.name}`);
|
|
83
58
|
});
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
| Option | Default | Description |
|
|
87
|
-
|--------|---------|-------------|
|
|
88
|
-
| `heartbeatTimeoutMs` | 60000 | Time before worker is marked unhealthy |
|
|
89
|
-
| `healthCheckIntervalMs` | 10000 | Frequency of health checks |
|
|
90
|
-
| `heartbeatIntervalMs` | 15000 | Heartbeat interval communicated to workers |
|
|
91
|
-
|
|
92
|
-
#### Disconnection Handling
|
|
93
59
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
server.onWorkerDisconnected((worker, pendingRequestIds) => {
|
|
98
|
-
// Reassign pending requests as needed
|
|
99
|
-
console.log(`Pending: ${[...pendingRequestIds].join(", ")}`);
|
|
60
|
+
// Called when a worker disconnects
|
|
61
|
+
const unsubscribeDisconnected = server.onWorkerDisconnected((worker, pending) => {
|
|
62
|
+
console.log(`Worker disconnected with ${pending.size} pending requests`);
|
|
100
63
|
});
|
|
101
64
|
```
|
|
102
65
|
|
|
103
|
-
|
|
66
|
+
#### Message Handling
|
|
104
67
|
|
|
105
|
-
|
|
68
|
+
```typescript
|
|
69
|
+
// Register handlers for domain-specific messages by type
|
|
70
|
+
server.onWorkerMessage("work_request", (worker, message) => {
|
|
71
|
+
// Process work request from worker
|
|
72
|
+
});
|
|
106
73
|
|
|
107
|
-
|
|
74
|
+
server.onWorkerMessage("status_update", (worker, message) => {
|
|
75
|
+
// Handle status updates from worker
|
|
76
|
+
});
|
|
77
|
+
```
|
|
108
78
|
|
|
109
|
-
|
|
79
|
+
#### Sending Messages
|
|
110
80
|
|
|
111
81
|
```typescript
|
|
112
|
-
//
|
|
113
|
-
const
|
|
114
|
-
if (worker) {
|
|
115
|
-
server.trackRequest(worker.id, "req-1");
|
|
116
|
-
server.send(worker.id, { type: "work_request", requestId: "req-1" });
|
|
117
|
-
}
|
|
82
|
+
// Send to a specific worker
|
|
83
|
+
const success = server.send("worker-1", { type: "stop", reason: "shutdown" });
|
|
118
84
|
|
|
119
|
-
//
|
|
120
|
-
server.
|
|
121
|
-
server.releaseRequest(message.requestId, { incrementCompleted: true });
|
|
122
|
-
});
|
|
85
|
+
// Broadcast to all connected workers
|
|
86
|
+
server.broadcast({ type: "maintenance_start" });
|
|
123
87
|
```
|
|
124
88
|
|
|
125
|
-
####
|
|
126
|
-
|
|
127
|
-
Workers are selected based on capacity and model support.
|
|
89
|
+
#### Pool Queries
|
|
128
90
|
|
|
129
91
|
```typescript
|
|
130
|
-
// Get least-loaded worker supporting a model
|
|
131
|
-
const worker = server.getAvailableWorker("
|
|
132
|
-
// → least-loaded worker that supports "gpt-4"
|
|
92
|
+
// Get least-loaded worker supporting a specific model
|
|
93
|
+
const worker = server.getAvailableWorker("sonnet-3.5");
|
|
133
94
|
|
|
134
95
|
// Get any available worker (model-agnostic)
|
|
135
96
|
const anyWorker = server.getAnyAvailableWorker();
|
|
136
|
-
// → any worker (Available or Busy status)
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
Workers are marked `Busy` when `activeRequests >= maxConcurrentRequests`.
|
|
140
97
|
|
|
141
|
-
|
|
98
|
+
// Get all worker info
|
|
99
|
+
const workers = server.getWorkerInfo(); // Returns WorkerInfo[]
|
|
100
|
+
```
|
|
142
101
|
|
|
143
|
-
|
|
102
|
+
#### Request Tracking
|
|
144
103
|
|
|
145
104
|
```typescript
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
models: [{ modelId: "gpt-4", ... }],
|
|
149
|
-
maxConcurrentRequests: 5,
|
|
150
|
-
concurrencyLimits: {
|
|
151
|
-
inference: 2,
|
|
152
|
-
embedding: 4,
|
|
153
|
-
}
|
|
154
|
-
}
|
|
105
|
+
// Track a request assigned to a worker
|
|
106
|
+
server.trackRequest("worker-1", "req-123", "inference");
|
|
155
107
|
|
|
156
|
-
//
|
|
157
|
-
server.
|
|
108
|
+
// Release a completed request
|
|
109
|
+
server.releaseRequest("req-123", { incrementCompleted: true });
|
|
158
110
|
```
|
|
159
111
|
|
|
160
|
-
###
|
|
112
|
+
### WorkerPool
|
|
161
113
|
|
|
162
|
-
|
|
114
|
+
Manages worker lifecycle, request tracking, health checks, and message routing.
|
|
163
115
|
|
|
164
|
-
|
|
165
|
-
server.onWorkerMessage("work_complete", (worker, message) => {
|
|
166
|
-
console.log(`Worker ${worker.id} completed ${message.requestId}`);
|
|
167
|
-
});
|
|
116
|
+
#### Worker Status
|
|
168
117
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
118
|
+
Workers transition automatically between states:
|
|
119
|
+
- `available`: Ready to accept new requests
|
|
120
|
+
- `busy`: At maximum concurrent request capacity
|
|
121
|
+
- `draining`: In the process of shutting down
|
|
122
|
+
- `unhealthy`: Heartbeat missed beyond timeout threshold
|
|
173
123
|
|
|
174
|
-
|
|
124
|
+
#### Pool Operations
|
|
175
125
|
|
|
176
126
|
```typescript
|
|
177
|
-
|
|
178
|
-
// later...
|
|
179
|
-
unsubscribe();
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### Sending Messages
|
|
127
|
+
import { WorkerPool, WorkerStatus, type ConnectedWorker } from "@hardlydifficult/worker-server";
|
|
183
128
|
|
|
184
|
-
|
|
129
|
+
const pool = new WorkerPool();
|
|
185
130
|
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
// Returns false if worker not found or WebSocket not open
|
|
189
|
-
```
|
|
131
|
+
// Get the least-loaded available worker supporting a model
|
|
132
|
+
const worker = pool.getAvailableWorker("sonnet-3.5", "inference");
|
|
190
133
|
|
|
191
|
-
|
|
134
|
+
// Get any available or busy worker
|
|
135
|
+
const anyWorker = pool.getAnyAvailableWorker();
|
|
192
136
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
```
|
|
137
|
+
// Get count statistics
|
|
138
|
+
const total = pool.getCount();
|
|
139
|
+
const available = pool.getAvailableCount();
|
|
197
140
|
|
|
198
|
-
|
|
141
|
+
// Get worker info (without WebSocket reference)
|
|
142
|
+
const workers = pool.getWorkerInfoList();
|
|
199
143
|
|
|
200
|
-
|
|
144
|
+
// Track and release requests
|
|
145
|
+
pool.trackRequest("worker-1", "req-123");
|
|
146
|
+
pool.releaseRequest("req-123");
|
|
201
147
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
server.addWebSocketEndpoint("/ws/metrics", (ws) => {
|
|
205
|
-
ws.on("message", (data) => {
|
|
206
|
-
console.log("Metrics client message:", data.toString());
|
|
207
|
-
});
|
|
208
|
-
});
|
|
148
|
+
// Check for unhealthy workers
|
|
149
|
+
const deadWorkerIds = pool.checkHealth(60_000); // 60-second timeout
|
|
209
150
|
|
|
210
|
-
//
|
|
151
|
+
// Send and broadcast messages
|
|
152
|
+
pool.send("worker-1", { type: "ping" });
|
|
153
|
+
pool.broadcast({ type: "shutdown" });
|
|
211
154
|
```
|
|
212
155
|
|
|
213
|
-
|
|
156
|
+
### ConnectionHandler
|
|
157
|
+
|
|
158
|
+
Handles WebSocket connection lifecycle and protocol message routing.
|
|
159
|
+
|
|
160
|
+
#### Message Routing
|
|
214
161
|
|
|
215
162
|
```typescript
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
res.end(JSON.stringify({ status: "ok" }));
|
|
220
|
-
return true;
|
|
221
|
-
}
|
|
222
|
-
return false; // continue to next handler or 404
|
|
223
|
-
});
|
|
163
|
+
import { ConnectionHandler } from "@hardlydifficult/worker-server";
|
|
164
|
+
|
|
165
|
+
const handler = new ConnectionHandler(pool, config, logger);
|
|
224
166
|
|
|
225
|
-
//
|
|
167
|
+
// Register handlers for custom message types
|
|
168
|
+
const unregister = handler.onMessage("custom_type", (worker, message) => {
|
|
169
|
+
console.log(`Received from ${worker.id}:`, message);
|
|
170
|
+
});
|
|
226
171
|
```
|
|
227
172
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
### `WorkerServer`
|
|
231
|
-
|
|
232
|
-
Main server class for managing worker connections.
|
|
233
|
-
|
|
234
|
-
| Method | Description |
|
|
235
|
-
|--------|-------------|
|
|
236
|
-
| `onWorkerConnected(handler)` | Register handler for worker registration events |
|
|
237
|
-
| `onWorkerDisconnected(handler)` | Register handler for worker disconnection events |
|
|
238
|
-
| `onWorkerMessage(type, handler)` | Register handler for a specific message type |
|
|
239
|
-
| `send(workerId, message)` | Send a JSON message to a specific worker |
|
|
240
|
-
| `broadcast(message)` | Broadcast a JSON message to all workers |
|
|
241
|
-
| `getAvailableWorker(model, category?)` | Get least-loaded worker supporting model |
|
|
242
|
-
| `getAnyAvailableWorker()` | Get any available/Busy worker |
|
|
243
|
-
| `getWorkerCount()` | Total connected worker count |
|
|
244
|
-
| `getAvailableWorkerCount()` | Available worker count |
|
|
245
|
-
| `getWorkerInfo()` | Get public info about all workers |
|
|
246
|
-
| `trackRequest(workerId, requestId, category?)` | Track request as in-progress |
|
|
247
|
-
| `releaseRequest(requestId, options?)` | Release tracked request |
|
|
248
|
-
| `addHttpHandler(handler)` | Add HTTP request handler |
|
|
249
|
-
| `addWebSocketEndpoint(path, handler)` | Add custom WebSocket endpoint |
|
|
250
|
-
| `start()` | Start HTTP + WebSocket server |
|
|
251
|
-
| `stop()` | Stop server and close all connections |
|
|
252
|
-
|
|
253
|
-
### `WorkerPool`
|
|
254
|
-
|
|
255
|
-
Internal pool manager with public helpers.
|
|
256
|
-
|
|
257
|
-
| Method | Description |
|
|
258
|
-
|--------|-------------|
|
|
259
|
-
| `add(worker)` | Add a connected worker to the pool |
|
|
260
|
-
| `remove(id)` | Remove worker by ID |
|
|
261
|
-
| `get(id)` | Get worker by ID |
|
|
262
|
-
| `has(id)` | Check if worker is in pool |
|
|
263
|
-
| `getAvailableWorker(model, category?)` | Get available worker by model |
|
|
264
|
-
| `getAnyAvailableWorker()` | Get any available/Busy worker |
|
|
265
|
-
| `getCount()` | Total worker count |
|
|
266
|
-
| `getAvailableCount()` | Available worker count |
|
|
267
|
-
| `getWorkerInfoList()` | Get public info for all workers |
|
|
268
|
-
| `checkHealth(timeoutMs)` | Check worker health and return dead IDs |
|
|
269
|
-
| `send(workerId, message)` | Send message to worker |
|
|
270
|
-
| `broadcast(message)` | Broadcast to all workers |
|
|
271
|
-
| `closeAll()` | Close all worker connections |
|
|
272
|
-
|
|
273
|
-
### `toWorkerInfo(worker)`
|
|
274
|
-
|
|
275
|
-
Converts internal `ConnectedWorker` to public `WorkerInfo`.
|
|
173
|
+
#### Event Handlers
|
|
276
174
|
|
|
277
175
|
```typescript
|
|
278
|
-
|
|
176
|
+
handler.onWorkerConnected((worker) => {
|
|
177
|
+
console.log("Worker connected:", worker.id);
|
|
178
|
+
});
|
|
279
179
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
180
|
+
handler.onWorkerDisconnected((worker, pending) => {
|
|
181
|
+
console.log("Worker disconnected with pending:", pending.size);
|
|
182
|
+
});
|
|
283
183
|
```
|
|
284
184
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
| Type | Description |
|
|
288
|
-
|------|-------------|
|
|
289
|
-
| `WorkerStatus` | `available`, `busy`, `draining`, `unhealthy` |
|
|
290
|
-
| `ModelInfo` | Model capabilities and metadata |
|
|
291
|
-
| `WorkerCapabilities` | Worker capacity, models, and concurrency limits |
|
|
292
|
-
| `WorkerInfo` | Public worker state |
|
|
293
|
-
| `ConnectedWorker` | Internal state (includes WebSocket) |
|
|
294
|
-
| `WorkerServerOptions` | Configuration for `WorkerServer` |
|
|
295
|
-
| `WorkerServerLogger` | Logger interface |
|
|
296
|
-
| `HttpRequestHandler` | HTTP request handler type |
|
|
297
|
-
| `WorkerMessageHandler<T>` | Typed message handler |
|
|
298
|
-
| `WorkerConnectedHandler` | Worker connected event handler |
|
|
299
|
-
| `WorkerDisconnectedHandler` | Worker disconnected event handler |
|
|
300
|
-
| `WebSocketConnectionHandler` | Custom WebSocket endpoint handler |
|
|
185
|
+
## Types and Interfaces
|
|
301
186
|
|
|
302
|
-
###
|
|
187
|
+
### WorkerInfo
|
|
303
188
|
|
|
304
|
-
|
|
189
|
+
Public worker metadata exposed to consumers:
|
|
305
190
|
|
|
306
191
|
```typescript
|
|
307
|
-
{
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
192
|
+
interface WorkerInfo {
|
|
193
|
+
id: string;
|
|
194
|
+
name: string;
|
|
195
|
+
status: WorkerStatus;
|
|
196
|
+
capabilities: WorkerCapabilities;
|
|
197
|
+
sessionId: string;
|
|
198
|
+
connectedAt: Date;
|
|
199
|
+
lastHeartbeat: Date;
|
|
200
|
+
activeRequests: number;
|
|
201
|
+
completedRequests: number;
|
|
202
|
+
pendingRequestIds: ReadonlySet<string>;
|
|
203
|
+
categoryActiveRequests: ReadonlyMap<string, number>;
|
|
311
204
|
}
|
|
312
205
|
```
|
|
313
206
|
|
|
314
|
-
###
|
|
315
|
-
|
|
316
|
-
#### `safeCompare(a, b)`
|
|
207
|
+
### WorkerCapabilities
|
|
317
208
|
|
|
318
|
-
|
|
209
|
+
Describes what a worker can do:
|
|
319
210
|
|
|
320
211
|
```typescript
|
|
321
|
-
|
|
212
|
+
interface WorkerCapabilities {
|
|
213
|
+
models: ModelInfo[];
|
|
214
|
+
maxConcurrentRequests: number;
|
|
215
|
+
metadata?: Record<string, unknown>;
|
|
216
|
+
concurrencyLimits?: Record<string, number>; // per-category limits
|
|
217
|
+
}
|
|
322
218
|
|
|
323
|
-
|
|
219
|
+
interface ModelInfo {
|
|
220
|
+
modelId: string;
|
|
221
|
+
displayName: string;
|
|
222
|
+
maxContextTokens: number;
|
|
223
|
+
maxOutputTokens: number;
|
|
224
|
+
supportsStreaming: boolean;
|
|
225
|
+
supportsVision?: boolean;
|
|
226
|
+
supportsTools?: boolean;
|
|
227
|
+
}
|
|
324
228
|
```
|
|
325
229
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
"workerName": "My Worker",
|
|
337
|
-
"capabilities": {
|
|
338
|
-
"models": [{
|
|
339
|
-
"modelId": "gpt-4",
|
|
340
|
-
"displayName": "GPT-4",
|
|
341
|
-
"maxContextTokens": 8192,
|
|
342
|
-
"maxOutputTokens": 4096,
|
|
343
|
-
"supportsStreaming": true
|
|
344
|
-
}],
|
|
345
|
-
"maxConcurrentRequests": 5,
|
|
346
|
-
"concurrencyLimits": {
|
|
347
|
-
"inference": 2,
|
|
348
|
-
"embedding": 4
|
|
349
|
-
}
|
|
350
|
-
},
|
|
351
|
-
"authToken": "optional"
|
|
230
|
+
### Configuration Options
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
interface WorkerServerOptions {
|
|
234
|
+
port: number;
|
|
235
|
+
authToken?: string;
|
|
236
|
+
heartbeatTimeoutMs?: number; // default: 60000
|
|
237
|
+
healthCheckIntervalMs?: number; // default: 10000
|
|
238
|
+
heartbeatIntervalMs?: number; // default: 15000
|
|
239
|
+
logger?: WorkerServerLogger;
|
|
352
240
|
}
|
|
353
241
|
```
|
|
354
242
|
|
|
355
|
-
|
|
243
|
+
### Logger Interface
|
|
356
244
|
|
|
357
|
-
```
|
|
358
|
-
{
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
245
|
+
```typescript
|
|
246
|
+
interface WorkerServerLogger {
|
|
247
|
+
debug(message: string, context?: Record<string, unknown>): void;
|
|
248
|
+
info(message: string, context?: Record<string, unknown>): void;
|
|
249
|
+
warn(message: string, context?: Record<string, unknown>): void;
|
|
250
|
+
error(message: string, context?: Record<string, unknown>): void;
|
|
363
251
|
}
|
|
364
252
|
```
|
|
365
253
|
|
|
366
|
-
|
|
254
|
+
## Advanced Features
|
|
367
255
|
|
|
368
|
-
|
|
256
|
+
### HTTP Endpoints
|
|
369
257
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
"
|
|
375
|
-
}
|
|
258
|
+
Custom HTTP handlers can be added:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
server.addHttpHandler(async (req, res) => {
|
|
262
|
+
if (req.url === "/health") {
|
|
263
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
264
|
+
res.end(JSON.stringify({ status: "ok" }));
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
return false; // continue to next handler
|
|
268
|
+
});
|
|
376
269
|
```
|
|
377
270
|
|
|
378
|
-
|
|
271
|
+
### Custom WebSocket Endpoints
|
|
379
272
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
"
|
|
385
|
-
|
|
273
|
+
Additional WebSocket paths can be handled:
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
server.addWebSocketEndpoint("/ws/admin", (ws) => {
|
|
277
|
+
ws.on("message", (data) => {
|
|
278
|
+
// Handle admin WebSocket messages
|
|
279
|
+
});
|
|
280
|
+
});
|
|
386
281
|
```
|
|
387
282
|
|
|
388
|
-
###
|
|
283
|
+
### Authentication
|
|
389
284
|
|
|
390
|
-
|
|
391
|
-
- `Busy` → `Available` when `activeRequests < maxConcurrentRequests`
|
|
392
|
-
- Any → `Unhealthy` on heartbeat timeout
|
|
393
|
-
- `Unhealthy` → `Available/Busy` on heartbeat recovery
|
|
285
|
+
Optionally require authentication tokens from workers:
|
|
394
286
|
|
|
395
|
-
|
|
287
|
+
```typescript
|
|
288
|
+
const server = new WorkerServer({
|
|
289
|
+
port: 8080,
|
|
290
|
+
authToken: "your-secret-token"
|
|
291
|
+
});
|
|
396
292
|
|
|
397
|
-
|
|
293
|
+
// Workers must send registration with matching authToken
|
|
294
|
+
```
|
|
398
295
|
|
|
399
|
-
###
|
|
296
|
+
### Load Balancing with Category Limits
|
|
400
297
|
|
|
401
|
-
Workers
|
|
298
|
+
Workers can declare per-category concurrency limits:
|
|
402
299
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
300
|
+
```typescript
|
|
301
|
+
const capabilities = {
|
|
302
|
+
models: [{ modelId: "sonnet", ... }],
|
|
303
|
+
maxConcurrentRequests: 10,
|
|
304
|
+
concurrencyLimits: {
|
|
305
|
+
inference: 5, // max 5 concurrent inference requests
|
|
306
|
+
embeddings: 2 // max 2 concurrent embedding requests
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Requests are then tracked by category:
|
|
409
312
|
|
|
410
|
-
|
|
313
|
+
```typescript
|
|
314
|
+
server.trackRequest("worker-1", "req-1", "inference");
|
|
315
|
+
server.releaseRequest("req-1"); // category looked up automatically
|
|
316
|
+
```
|