@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.
Files changed (2) hide show
  1. package/README.md +52 -24
  2. 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, WorkerStatus } from "@hardlydifficult/worker-server";
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 with status ${worker.status}`);
22
+ console.log(`Worker ${worker.name} connected`);
23
23
  });
24
24
 
25
- server.onWorkerMessage("work_result", (worker, message) => {
26
- console.log(`Worker ${worker.id} completed request:`, message);
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("Worker server listening on port", server.port);
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
- // Get any available worker (model-agnostic)
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
- // Worker info
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
- - `getAvailableWorker(model, category?)`: Returns least-loaded worker supporting the model, respecting per-category concurrency limits
156
- - `getAnyAvailableWorker()`: Returns any worker regardless of model (both Available and Busy)
157
- - `getAvailableSlotCount(model, category?)`: Total free slots across all available workers for the model
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
- const server = new WorkerServer({
238
- port: 19100,
239
- authToken: "secret-token",
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
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardlydifficult/worker-server",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [