@byok-sdk/server 0.3.0 → 0.4.1
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 +5 -3
- package/dist/hub.d.ts +2 -2
- package/dist/index.js +49 -9
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/package.json +14 -14
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ The self-hosted SaaS-side reference coordinator: pairing, authenticated device
|
|
|
4
4
|
HTTP/WebSocket/long-poll transport, task leasing, approvals, and in-memory
|
|
5
5
|
stores over the frozen v1 protocol.
|
|
6
6
|
|
|
7
|
-
Use `@byok-sdk/cloud` plus `@byok-sdk/cloud-
|
|
7
|
+
Use `@byok-sdk/cloud` plus `@byok-sdk/cloud-dataplane` for the durable hosted
|
|
8
8
|
composition.
|
|
9
9
|
|
|
10
10
|
Toolset-aware dispatch names logical device-local MCP toolsets; it never sends
|
|
@@ -21,6 +21,8 @@ const task = await server.dispatch({
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
The self-hosted coordinator rejects this call before task creation unless the
|
|
24
|
-
live device advertises `toolset-selection
|
|
24
|
+
live device advertises `toolset-selection` and its `configuredToolsets`
|
|
25
|
+
inventory contains every required ID. `machines.list()` projects that same
|
|
26
|
+
logical-ID-only inventory; MCP commands and credentials remain device-local.
|
|
25
27
|
|
|
26
|
-
MIT licensed. Node.js 22.
|
|
28
|
+
MIT licensed. Node.js 22.22.0 or newer.
|
package/dist/hub.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { WebSocket } from 'ws';
|
|
2
|
-
import { type Envelope, type RuntimeId, type RuntimeInfo, type TaskState } from '@byok-sdk/protocol';
|
|
2
|
+
import { type Envelope, type RuntimeId, type RuntimeInfo, type TaskState, type ToolsetId } from '@byok-sdk/protocol';
|
|
3
3
|
import type { DeviceRegistry } from './auth';
|
|
4
4
|
import { RateLimiter } from './rate-limiter';
|
|
5
5
|
import type { TaskStore } from './task-store';
|
|
@@ -276,7 +276,7 @@ export declare class ConnectionHub {
|
|
|
276
276
|
* connection this hub never learns capabilities for simply reads back
|
|
277
277
|
* `undefined` from {@link getDeviceCapabilities}.
|
|
278
278
|
*/
|
|
279
|
-
registerConnection(deviceId: string, ws: WebSocket, runtimes: RuntimeInfo[] | undefined, capabilities?: readonly string[]): void;
|
|
279
|
+
registerConnection(deviceId: string, ws: WebSocket, runtimes: RuntimeInfo[] | undefined, capabilities?: readonly string[], configuredToolsets?: readonly ToolsetId[]): void;
|
|
280
280
|
sendConnAck(deviceId: string, capabilities: string[]): void;
|
|
281
281
|
/**
|
|
282
282
|
* Reconnection procedure step 3 (§9): redeliver, in `seq` order, every
|
package/dist/index.js
CHANGED
|
@@ -489,7 +489,7 @@ function buildHonoApp(deps) {
|
|
|
489
489
|
cursor = parsedCursor;
|
|
490
490
|
}
|
|
491
491
|
const result = await deps.hub.pollEvents(principal.deviceId, cursor, deps.longPollHoldMs);
|
|
492
|
-
const response = result;
|
|
492
|
+
const response = { ...result, capabilities: [...CAPABILITY_FLAGS] };
|
|
493
493
|
return c.json(response, 200);
|
|
494
494
|
});
|
|
495
495
|
app.post(BYOK_MESSAGES_PATH, async (c) => {
|
|
@@ -844,9 +844,16 @@ var ConnectionHub = class {
|
|
|
844
844
|
* connection this hub never learns capabilities for simply reads back
|
|
845
845
|
* `undefined` from {@link getDeviceCapabilities}.
|
|
846
846
|
*/
|
|
847
|
-
registerConnection(deviceId, ws, runtimes, capabilities) {
|
|
847
|
+
registerConnection(deviceId, ws, runtimes, capabilities, configuredToolsets) {
|
|
848
848
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
849
|
-
this.connections.set(deviceId, {
|
|
849
|
+
this.connections.set(deviceId, {
|
|
850
|
+
ws,
|
|
851
|
+
connected: true,
|
|
852
|
+
lastSeen: at,
|
|
853
|
+
runtimes,
|
|
854
|
+
capabilities,
|
|
855
|
+
configuredToolsets
|
|
856
|
+
});
|
|
850
857
|
this.serverEvents.push({ kind: "device.connected", deviceId, at });
|
|
851
858
|
this.settleLongPollWaiter(deviceId);
|
|
852
859
|
}
|
|
@@ -941,7 +948,13 @@ var ConnectionHub = class {
|
|
|
941
948
|
const wasFreshlyConnected = !conn || conn.ws !== void 0 || !conn.connected;
|
|
942
949
|
if (conn?.ws) {
|
|
943
950
|
const ws = conn.ws;
|
|
944
|
-
this.connections.set(deviceId, {
|
|
951
|
+
this.connections.set(deviceId, {
|
|
952
|
+
connected: true,
|
|
953
|
+
lastSeen: at,
|
|
954
|
+
runtimes: conn.runtimes,
|
|
955
|
+
capabilities: conn.capabilities,
|
|
956
|
+
configuredToolsets: conn.configuredToolsets
|
|
957
|
+
});
|
|
945
958
|
ws.close(1e3, "superseded by long-poll connection");
|
|
946
959
|
} else if (!conn) {
|
|
947
960
|
this.connections.set(deviceId, { connected: true, lastSeen: at });
|
|
@@ -1671,7 +1684,7 @@ var ConnectionHub = class {
|
|
|
1671
1684
|
async dispatch(input) {
|
|
1672
1685
|
const dispatchSelection = input.dispatchSelection === void 0 ? void 0 : DispatchSelectionSchema.parse(input.dispatchSelection);
|
|
1673
1686
|
const requiredToolsets = input.requiredToolsets === void 0 ? void 0 : RequiredToolsetsSchema.parse(input.requiredToolsets);
|
|
1674
|
-
const deviceId = input.deviceId ?? this.pickFirstConnectedDevice();
|
|
1687
|
+
const deviceId = input.deviceId ?? this.pickFirstConnectedDevice(requiredToolsets);
|
|
1675
1688
|
if (!deviceId || !this.connections.get(deviceId)?.connected) {
|
|
1676
1689
|
throw new Error(
|
|
1677
1690
|
deviceId ? `device ${deviceId} is not connected` : "no connected device to dispatch to (M0 does not queue tasks until a device connects)"
|
|
@@ -1687,6 +1700,21 @@ var ConnectionHub = class {
|
|
|
1687
1700
|
`device ${deviceId} did not advertise toolset-selection capability; refusing a task whose semantics require local MCP tools`
|
|
1688
1701
|
);
|
|
1689
1702
|
}
|
|
1703
|
+
if (requiredToolsets !== void 0) {
|
|
1704
|
+
const configuredToolsets = this.connections.get(deviceId)?.configuredToolsets;
|
|
1705
|
+
if (configuredToolsets === void 0) {
|
|
1706
|
+
throw new Error(
|
|
1707
|
+
`device ${deviceId} did not advertise its configured toolset inventory; refusing to guess from runtime capability`
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
const configured = new Set(configuredToolsets);
|
|
1711
|
+
const missing = requiredToolsets.filter((toolsetId) => !configured.has(toolsetId));
|
|
1712
|
+
if (missing.length > 0) {
|
|
1713
|
+
throw new Error(
|
|
1714
|
+
`device ${deviceId} is missing required MCP toolset(s): ${missing.join(", ")}`
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1690
1718
|
if (dispatchSelection !== void 0 && input.runtime !== void 0 && input.runtime !== dispatchSelection.runtimeId) {
|
|
1691
1719
|
throw new Error(
|
|
1692
1720
|
`dispatch runtime ${input.runtime} does not match dispatchSelection.runtimeId ${dispatchSelection.runtimeId}`
|
|
@@ -1879,9 +1907,14 @@ var ConnectionHub = class {
|
|
|
1879
1907
|
}
|
|
1880
1908
|
this.sendToDevice(record.deviceId, "task.steer", { text }, { taskId });
|
|
1881
1909
|
}
|
|
1882
|
-
pickFirstConnectedDevice() {
|
|
1910
|
+
pickFirstConnectedDevice(requiredToolsets) {
|
|
1883
1911
|
for (const [deviceId, conn] of this.connections) {
|
|
1884
|
-
if (conn.connected)
|
|
1912
|
+
if (!conn.connected) continue;
|
|
1913
|
+
if (requiredToolsets === void 0) return deviceId;
|
|
1914
|
+
if (!conn.capabilities?.includes("toolset-selection")) continue;
|
|
1915
|
+
if (conn.configuredToolsets === void 0) continue;
|
|
1916
|
+
const configured = new Set(conn.configuredToolsets);
|
|
1917
|
+
if (requiredToolsets.every((toolsetId) => configured.has(toolsetId))) return deviceId;
|
|
1885
1918
|
}
|
|
1886
1919
|
return void 0;
|
|
1887
1920
|
}
|
|
@@ -1964,7 +1997,8 @@ var ConnectionHub = class {
|
|
|
1964
1997
|
deviceName,
|
|
1965
1998
|
connected: conn?.connected ?? false,
|
|
1966
1999
|
lastSeen: conn?.lastSeen,
|
|
1967
|
-
runtimes: conn?.runtimes
|
|
2000
|
+
runtimes: conn?.runtimes,
|
|
2001
|
+
configuredToolsets: conn?.configuredToolsets ? [...conn.configuredToolsets] : void 0
|
|
1968
2002
|
};
|
|
1969
2003
|
});
|
|
1970
2004
|
}
|
|
@@ -2212,7 +2246,13 @@ function handleConnection(ws, principal, deps) {
|
|
|
2212
2246
|
return;
|
|
2213
2247
|
}
|
|
2214
2248
|
helloReceived = true;
|
|
2215
|
-
deps.hub.registerConnection(
|
|
2249
|
+
deps.hub.registerConnection(
|
|
2250
|
+
deviceId,
|
|
2251
|
+
ws,
|
|
2252
|
+
payload.runtimes,
|
|
2253
|
+
payload.capabilities,
|
|
2254
|
+
payload.configuredToolsets
|
|
2255
|
+
);
|
|
2216
2256
|
deps.hub.sendConnAck(deviceId, SUPPORTED_CAPABILITIES);
|
|
2217
2257
|
if (payload.cursor !== void 0) {
|
|
2218
2258
|
deps.hub.redeliverAfterReconnect(deviceId, payload.cursor);
|