@hardlydifficult/worker-server 1.0.11 → 1.0.12
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 +52 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npm install @hardlydifficult/worker-server
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
|
-
import { WorkerServer
|
|
14
|
+
import { WorkerServer } from "@hardlydifficult/worker-server";
|
|
15
15
|
|
|
16
16
|
const server = new WorkerServer({
|
|
17
17
|
port: 19100,
|
|
@@ -19,15 +19,21 @@ const server = new WorkerServer({
|
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
server.onWorkerConnected((worker) => {
|
|
22
|
-
console.log(`Worker ${worker.name} connected
|
|
22
|
+
console.log(`Worker ${worker.name} connected`);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
server.onWorkerMessage("
|
|
26
|
-
console.log(`Worker ${worker.id} completed
|
|
25
|
+
server.onWorkerMessage("work_complete", (worker, message) => {
|
|
26
|
+
console.log(`Worker ${worker.id} completed:`, message);
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
await server.start();
|
|
30
|
-
console.log("
|
|
30
|
+
console.log("Server listening on port", server.port);
|
|
31
|
+
|
|
32
|
+
// Send a message to a worker
|
|
33
|
+
const worker = server.getAvailableWorker("sonnet");
|
|
34
|
+
if (worker) {
|
|
35
|
+
server.send(worker.id, { type: "work_request", requestId: "req-123" });
|
|
36
|
+
}
|
|
31
37
|
```
|
|
32
38
|
|
|
33
39
|
## Core Concepts
|
|
@@ -37,19 +43,16 @@ console.log("Worker server listening on port", server.port);
|
|
|
37
43
|
Main entry point for managing worker connections via WebSocket.
|
|
38
44
|
|
|
39
45
|
```typescript
|
|
40
|
-
import { WorkerServer } from "@hardlydifficult/worker-server";
|
|
46
|
+
import { WorkerServer, WorkerStatus } from "@hardlydifficult/worker-server";
|
|
41
47
|
|
|
42
48
|
const server = new WorkerServer({
|
|
43
49
|
port: 19100,
|
|
44
|
-
authToken: "secret", // optional
|
|
45
50
|
heartbeatTimeoutMs: 60_000,
|
|
46
51
|
healthCheckIntervalMs: 10_000,
|
|
47
52
|
heartbeatIntervalMs: 15_000,
|
|
48
|
-
logger: myLogger,
|
|
49
53
|
});
|
|
50
54
|
|
|
51
55
|
await server.start();
|
|
52
|
-
// Handle connections, messages, and shutdown
|
|
53
56
|
await server.stop();
|
|
54
57
|
```
|
|
55
58
|
|
|
@@ -87,17 +90,26 @@ server.broadcast({ type: "shutdown" });
|
|
|
87
90
|
|
|
88
91
|
#### Worker Selection & Pool Queries
|
|
89
92
|
|
|
93
|
+
| Method | Description |
|
|
94
|
+
|--------|-------------|
|
|
95
|
+
| `getAvailableWorker(model, category?)` | Least-loaded worker supporting the model |
|
|
96
|
+
| `getAnyAvailableWorker()` | Any available or busy worker (model-agnostic) |
|
|
97
|
+
| `getAvailableSlotCount(model, category?)` | Total free slots across all available workers |
|
|
98
|
+
| `getWorkerCount()` | Total connected workers |
|
|
99
|
+
| `getAvailableWorkerCount()` | Available workers count |
|
|
100
|
+
| `getWorkerInfo()` | Public info for all workers |
|
|
101
|
+
|
|
90
102
|
```typescript
|
|
91
103
|
// Get least-loaded worker supporting a model
|
|
92
104
|
const worker = server.getAvailableWorker("sonnet");
|
|
105
|
+
if (worker) {
|
|
106
|
+
server.trackRequest(worker.id, "req-123", "local");
|
|
107
|
+
}
|
|
93
108
|
|
|
94
|
-
//
|
|
95
|
-
const any = server.getAnyAvailableWorker();
|
|
96
|
-
|
|
97
|
-
// Slot counts
|
|
109
|
+
// Slot counts with category-aware limits
|
|
98
110
|
console.log("Available slots:", server.getAvailableSlotCount("sonnet", "local"));
|
|
99
111
|
|
|
100
|
-
//
|
|
112
|
+
// View all workers
|
|
101
113
|
for (const info of server.getWorkerInfo()) {
|
|
102
114
|
console.log(`${info.name}: ${info.status} (${info.activeRequests}/${info.capabilities.maxConcurrentRequests})`);
|
|
103
115
|
}
|
|
@@ -141,10 +153,11 @@ server.addWebSocketEndpoint("/ws/dashboard", (ws) => {
|
|
|
141
153
|
Low-level pool manager for worker state and selection.
|
|
142
154
|
|
|
143
155
|
```typescript
|
|
144
|
-
import { WorkerPool, WorkerStatus } from "@hardlydifficult/worker-server";
|
|
156
|
+
import { WorkerPool, toWorkerInfo, WorkerStatus } from "@hardlydifficult/worker-server";
|
|
145
157
|
|
|
146
158
|
const pool = new WorkerPool(logger);
|
|
147
159
|
|
|
160
|
+
// Add/remove workers
|
|
148
161
|
pool.add(worker);
|
|
149
162
|
pool.remove(workerId);
|
|
150
163
|
const worker = pool.get(workerId);
|
|
@@ -152,9 +165,14 @@ const worker = pool.get(workerId);
|
|
|
152
165
|
|
|
153
166
|
#### Selection Logic
|
|
154
167
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
168
|
+
| Method | Description |
|
|
169
|
+
|--------|-------------|
|
|
170
|
+
| `getAvailableWorker(model, category?)` | Least-loaded worker supporting the model, respecting per-category concurrency limits |
|
|
171
|
+
| `getAnyAvailableWorker()` | Any available or busy worker (model-agnostic) |
|
|
172
|
+
| `getAvailableSlotCount(model, category?)` | Total free slots across all available workers for the model |
|
|
173
|
+
| `getCount()` | Total connected workers |
|
|
174
|
+
| `getAvailableCount()` | Available workers count |
|
|
175
|
+
| `getWorkerInfoList()` | Public info for all workers |
|
|
158
176
|
|
|
159
177
|
#### Request Management
|
|
160
178
|
|
|
@@ -173,6 +191,17 @@ const worker = pool.get(workerId);
|
|
|
173
191
|
|
|
174
192
|
Handles WebSocket lifecycle, registration, heartbeats, and message routing. Most consumers use `WorkerServer`, which encapsulates this.
|
|
175
193
|
|
|
194
|
+
### Message Protocol
|
|
195
|
+
|
|
196
|
+
Workers send JSON messages with a `type` field:
|
|
197
|
+
|
|
198
|
+
- `worker_registration` — Register with capabilities and optional `authToken`
|
|
199
|
+
- `heartbeat` — Send periodically to confirm liveness
|
|
200
|
+
|
|
201
|
+
The server responds with:
|
|
202
|
+
- `worker_registration_ack` — Success/failure with `sessionId` and `heartbeatIntervalMs`
|
|
203
|
+
- `heartbeat_ack` — Acknowledgment with `nextHeartbeatDeadline`
|
|
204
|
+
|
|
176
205
|
### Types & Interfaces
|
|
177
206
|
|
|
178
207
|
#### `WorkerStatus`
|
|
@@ -211,7 +240,7 @@ interface WorkerCapabilities {
|
|
|
211
240
|
models: ModelInfo[];
|
|
212
241
|
maxConcurrentRequests: number;
|
|
213
242
|
metadata?: Record<string, unknown>;
|
|
214
|
-
concurrencyLimits?: Record<string, number>;
|
|
243
|
+
concurrencyLimits?: Record<string, number>; // per-category limits
|
|
215
244
|
}
|
|
216
245
|
```
|
|
217
246
|
|
|
@@ -234,13 +263,12 @@ interface ModelInfo {
|
|
|
234
263
|
Authentication tokens are compared using timing-safe comparison to prevent brute-force attacks:
|
|
235
264
|
|
|
236
265
|
```typescript
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
});
|
|
266
|
+
import { safeCompare } from "@hardlydifficult/worker-server";
|
|
267
|
+
// Internally used by ConnectionHandler; exposed for testing
|
|
268
|
+
const valid = safeCompare("a", "b"); // false
|
|
241
269
|
```
|
|
242
270
|
|
|
243
|
-
Workers must send:
|
|
271
|
+
Workers must send the token in registration:
|
|
244
272
|
|
|
245
273
|
```json
|
|
246
274
|
{
|