@byok-sdk/server 0.8.1 → 0.9.0
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/hub.d.ts +18 -2
- package/dist/index.js +74 -5
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +22 -1
- package/package.json +3 -3
package/dist/hub.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { WebSocket } from 'ws';
|
|
2
|
-
import { type Envelope, type AgentHomeProjectionCompletionRequest, type AgentHomeProjectionReadback, type RuntimeId, type RuntimeInfo, type TaskState, type ToolsetId } from '@byok-sdk/protocol';
|
|
2
|
+
import { type Envelope, type AgentMessagePublishPayload, type AgentMessageServerContext, type AgentHomeProjectionCompletionRequest, type AgentHomeProjectionReadback, 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';
|
|
@@ -209,6 +209,7 @@ export declare class ConnectionHub {
|
|
|
209
209
|
* working unchanged.
|
|
210
210
|
*/
|
|
211
211
|
private readonly rateLimiter;
|
|
212
|
+
private readonly agentMessage?;
|
|
212
213
|
private readonly connections;
|
|
213
214
|
private readonly outboxes;
|
|
214
215
|
/** Idempotency window per device (N3) — recent inbound envelope ids, capped at {@link DEDUP_RING_CAPACITY}. */
|
|
@@ -218,6 +219,9 @@ export declare class ConnectionHub {
|
|
|
218
219
|
private readonly serverEvents;
|
|
219
220
|
/** First-write-wins reliable facts; the reference composition's bounded in-memory readback. */
|
|
220
221
|
private readonly agentEgressReceipts;
|
|
222
|
+
private readonly agentMessageRequirements;
|
|
223
|
+
private readonly agentMessageReceipts;
|
|
224
|
+
private readonly agentMessageTaskPayloads;
|
|
221
225
|
/** Accepted requests are the authority that later receipts/transfers must echo exactly. */
|
|
222
226
|
private readonly agentContentReadRequests;
|
|
223
227
|
/** Content-free explicit-read audit facts keyed by exact authenticated device/request identity. */
|
|
@@ -267,7 +271,17 @@ export declare class ConnectionHub {
|
|
|
267
271
|
* call site (this hub is constructed directly by several tests) keeps
|
|
268
272
|
* working unchanged.
|
|
269
273
|
*/
|
|
270
|
-
rateLimiter?: RateLimiter
|
|
274
|
+
rateLimiter?: RateLimiter, agentMessage?: {
|
|
275
|
+
consume(input: {
|
|
276
|
+
readonly deviceId: string;
|
|
277
|
+
readonly taskId: string;
|
|
278
|
+
readonly context: AgentMessageServerContext;
|
|
279
|
+
readonly payload: AgentMessagePublishPayload;
|
|
280
|
+
}): {
|
|
281
|
+
readonly outcome: 'accepted' | 'held' | 'refused';
|
|
282
|
+
readonly reasonCode?: string;
|
|
283
|
+
};
|
|
284
|
+
} | undefined);
|
|
271
285
|
/**
|
|
272
286
|
* Stop the task-lease reaper's sweep timer — called by `ByokServer.stop()`
|
|
273
287
|
* (`index.ts`) on shutdown. Idempotent: clearing an already-cleared
|
|
@@ -380,6 +394,8 @@ export declare class ConnectionHub {
|
|
|
380
394
|
* facts is rejected rather than treated as an update.
|
|
381
395
|
*/
|
|
382
396
|
private handleAgentEgressReliable;
|
|
397
|
+
private handleAgentMessagePublish;
|
|
398
|
+
private sendAgentMessageDisposition;
|
|
383
399
|
private handleAgentContentReceipt;
|
|
384
400
|
private sendAgentEgressAck;
|
|
385
401
|
private sendAgentContentReceiptAck;
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { mkdir, writeFile, readFile } from 'fs/promises';
|
|
|
5
5
|
import { mkdtempSync, mkdirSync, existsSync, chmodSync } from 'fs';
|
|
6
6
|
import { tmpdir } from 'os';
|
|
7
7
|
import path, { dirname } from 'path';
|
|
8
|
-
import { CAPABILITY_FLAGS, byokBlobContentPath, canTransition, PROTOCOL_VERSION, encodeEnvelope, DAEMON_TO_SERVER_TYPES, AGENT_EGRESS_POLICY_CAPABILITY, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, AgentRefSchema, AgentEgressPolicySchema, DispatchSelectionSchema, RequiredToolsetsSchema, STRICT_AGENT_ONLY_CAPABILITY, AGENT_EGRESS_FRESH_SESSION_CAPABILITY, AgentContentReadPayloadSchema, AgentHomeProjectionPayloadSchema, AGENT_HOME_PROJECTION_CAPABILITY, AgentHomeProjectionReadbackSchema, AgentHomeProjectionCompletionRequestSchema, createEnvelope, TASK_STATES, BYOK_PAIR_PATH, PairRequestSchema, PairResponseSchema, BYOK_CHALLENGE_PATH, ChallengeRequestSchema, BYOK_TOKEN_PATH, TokenRequestSchema, BYOK_BLOBS_PATH, CreateBlobRequestSchema, BYOK_BLOB_FINALIZE_ROUTE, BYOK_BLOB_URL_ROUTE, BYOK_BLOB_CONTENT_ROUTE, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, MessagesSendRequestSchema, BYOK_AGENT_HOME_PROJECTION_COMPLETION_ROUTE, AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, PairResponseTenantIdSchema, decodeEnvelope, BYOK_WS_PATH } from '@byok-sdk/protocol';
|
|
8
|
+
import { CAPABILITY_FLAGS, byokBlobContentPath, canTransition, PROTOCOL_VERSION, encodeEnvelope, DAEMON_TO_SERVER_TYPES, AGENT_EGRESS_POLICY_CAPABILITY, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, AGENT_MESSAGE_EGRESS_CAPABILITY, AgentRefSchema, AgentEgressPolicySchema, AgentMessageEgressRequirementSchema, TerminalProjectionSelectionSchema, AgentMessageServerContextSchema, DispatchSelectionSchema, RequiredToolsetsSchema, STRICT_AGENT_ONLY_CAPABILITY, AGENT_EGRESS_FRESH_SESSION_CAPABILITY, TERMINAL_PROJECTION_SELECTION_CAPABILITY, AgentContentReadPayloadSchema, AgentHomeProjectionPayloadSchema, AGENT_HOME_PROJECTION_CAPABILITY, AgentHomeProjectionReadbackSchema, AgentHomeProjectionCompletionRequestSchema, createEnvelope, TASK_STATES, BYOK_PAIR_PATH, PairRequestSchema, PairResponseSchema, BYOK_CHALLENGE_PATH, ChallengeRequestSchema, BYOK_TOKEN_PATH, TokenRequestSchema, BYOK_BLOBS_PATH, CreateBlobRequestSchema, BYOK_BLOB_FINALIZE_ROUTE, BYOK_BLOB_URL_ROUTE, BYOK_BLOB_CONTENT_ROUTE, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, MessagesSendRequestSchema, BYOK_AGENT_HOME_PROJECTION_COMPLETION_ROUTE, AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, PairResponseTenantIdSchema, decodeEnvelope, BYOK_WS_PATH } from '@byok-sdk/protocol';
|
|
9
9
|
import { Hono } from 'hono';
|
|
10
10
|
import { WebSocketServer } from 'ws';
|
|
11
11
|
import { createRequire } from 'module';
|
|
@@ -540,11 +540,12 @@ var AgentHomeProjectionCompletionError = class extends Error {
|
|
|
540
540
|
code;
|
|
541
541
|
};
|
|
542
542
|
var ConnectionHub = class {
|
|
543
|
-
constructor(taskStore, devices, taskLeaseMs, rateLimiter = new RateLimiter()) {
|
|
543
|
+
constructor(taskStore, devices, taskLeaseMs, rateLimiter = new RateLimiter(), agentMessage) {
|
|
544
544
|
this.taskStore = taskStore;
|
|
545
545
|
this.devices = devices;
|
|
546
546
|
this.taskLeaseMs = taskLeaseMs;
|
|
547
547
|
this.rateLimiter = rateLimiter;
|
|
548
|
+
this.agentMessage = agentMessage;
|
|
548
549
|
const sweepIntervalMs = Math.min(Math.max(taskLeaseMs, 10), MAX_LEASE_REAPER_SWEEP_INTERVAL_MS);
|
|
549
550
|
this.leaseReaperTimer = setInterval(() => this.sweepLeases(), sweepIntervalMs);
|
|
550
551
|
this.leaseReaperTimer.unref?.();
|
|
@@ -553,6 +554,7 @@ var ConnectionHub = class {
|
|
|
553
554
|
devices;
|
|
554
555
|
taskLeaseMs;
|
|
555
556
|
rateLimiter;
|
|
557
|
+
agentMessage;
|
|
556
558
|
connections = /* @__PURE__ */ new Map();
|
|
557
559
|
outboxes = /* @__PURE__ */ new Map();
|
|
558
560
|
/** Idempotency window per device (N3) — recent inbound envelope ids, capped at {@link DEDUP_RING_CAPACITY}. */
|
|
@@ -562,6 +564,9 @@ var ConnectionHub = class {
|
|
|
562
564
|
serverEvents = new AsyncEventQueue();
|
|
563
565
|
/** First-write-wins reliable facts; the reference composition's bounded in-memory readback. */
|
|
564
566
|
agentEgressReceipts = /* @__PURE__ */ new Map();
|
|
567
|
+
agentMessageRequirements = /* @__PURE__ */ new Map();
|
|
568
|
+
agentMessageReceipts = /* @__PURE__ */ new Map();
|
|
569
|
+
agentMessageTaskPayloads = /* @__PURE__ */ new Map();
|
|
565
570
|
/** Accepted requests are the authority that later receipts/transfers must echo exactly. */
|
|
566
571
|
agentContentReadRequests = /* @__PURE__ */ new Map();
|
|
567
572
|
/** Content-free explicit-read audit facts keyed by exact authenticated device/request identity. */
|
|
@@ -824,6 +829,9 @@ var ConnectionHub = class {
|
|
|
824
829
|
if (envelope.type === "agent.egress.reliable") {
|
|
825
830
|
return this.handleAgentEgressReliable(deviceId, envelope.payload);
|
|
826
831
|
}
|
|
832
|
+
if (envelope.type === "agent.message.publish") {
|
|
833
|
+
return this.handleAgentMessagePublish(deviceId, envelope.task_id, envelope.payload);
|
|
834
|
+
}
|
|
827
835
|
if (envelope.type === "agent.content.receipt") {
|
|
828
836
|
return this.handleAgentContentReceipt(deviceId, envelope.payload);
|
|
829
837
|
}
|
|
@@ -868,6 +876,40 @@ var ConnectionHub = class {
|
|
|
868
876
|
this.sendAgentEgressAck(deviceId, receipt);
|
|
869
877
|
return "accepted";
|
|
870
878
|
}
|
|
879
|
+
handleAgentMessagePublish(deviceId, taskId, payload) {
|
|
880
|
+
if (!this.hasDeviceCapabilities(deviceId, [AGENT_MESSAGE_EGRESS_CAPABILITY])) return "rejected";
|
|
881
|
+
const binding = this.agentMessageRequirements.get(taskId);
|
|
882
|
+
const record = this.taskStore.get(taskId);
|
|
883
|
+
if (binding === void 0 || record === void 0 || record.deviceId !== deviceId || binding.deviceId !== deviceId || !sameAgentRef(binding.agentRef, payload.agentRef) || binding.sessionRef !== void 0 && binding.sessionRef !== payload.sessionRef || binding.requirement.contract !== payload.contract || binding.requirement.contentType !== payload.contentType || payload.byteCount > binding.requirement.maxBytes) return "rejected";
|
|
884
|
+
const taskPayload = this.agentMessageTaskPayloads.get(taskId);
|
|
885
|
+
if (taskPayload !== void 0 && JSON.stringify(taskPayload) !== JSON.stringify(payload)) return "rejected";
|
|
886
|
+
if (taskPayload === void 0) this.agentMessageTaskPayloads.set(taskId, payload);
|
|
887
|
+
const key = `${deviceId}\0${payload.messageId}`;
|
|
888
|
+
const previous = this.agentMessageReceipts.get(key);
|
|
889
|
+
if (previous !== void 0) {
|
|
890
|
+
if (JSON.stringify(previous.payload) !== JSON.stringify(payload)) return "rejected";
|
|
891
|
+
this.sendAgentMessageDisposition(deviceId, taskId, previous.disposition);
|
|
892
|
+
return "duplicate";
|
|
893
|
+
}
|
|
894
|
+
const decision = this.agentMessage?.consume({ deviceId, taskId, context: binding.context, payload }) ?? { outcome: "held", reasonCode: "consumer_unavailable" };
|
|
895
|
+
const disposition = {
|
|
896
|
+
agentRef: payload.agentRef,
|
|
897
|
+
sessionRef: payload.sessionRef,
|
|
898
|
+
contract: payload.contract,
|
|
899
|
+
messageId: payload.messageId,
|
|
900
|
+
cursor: payload.cursor,
|
|
901
|
+
contentHash: payload.contentHash,
|
|
902
|
+
outcome: decision.outcome,
|
|
903
|
+
receiptId: crypto.randomUUID(),
|
|
904
|
+
...decision.reasonCode === void 0 ? {} : { reasonCode: decision.reasonCode }
|
|
905
|
+
};
|
|
906
|
+
this.agentMessageReceipts.set(key, { payload, disposition });
|
|
907
|
+
this.sendAgentMessageDisposition(deviceId, taskId, disposition);
|
|
908
|
+
return "accepted";
|
|
909
|
+
}
|
|
910
|
+
sendAgentMessageDisposition(deviceId, taskId, payload) {
|
|
911
|
+
this.sendToDevice(deviceId, "agent.message.disposition", payload, { taskId });
|
|
912
|
+
}
|
|
871
913
|
handleAgentContentReceipt(deviceId, payload) {
|
|
872
914
|
const capability = contentReadCapability(payload.surface);
|
|
873
915
|
if (!this.hasDeviceCapabilities(deviceId, [capability, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY])) return "rejected";
|
|
@@ -1627,6 +1669,12 @@ var ConnectionHub = class {
|
|
|
1627
1669
|
const sessionRef = "sessionRef" in input ? input.sessionRef : void 0;
|
|
1628
1670
|
const agentRef = input.agentRef === void 0 ? void 0 : AgentRefSchema.parse(input.agentRef);
|
|
1629
1671
|
const egressPolicy = input.egressPolicy === void 0 ? void 0 : AgentEgressPolicySchema.parse(input.egressPolicy);
|
|
1672
|
+
const messageEgress = input.messageEgress === void 0 ? void 0 : AgentMessageEgressRequirementSchema.parse(input.messageEgress);
|
|
1673
|
+
const terminalProjection = input.terminalProjection === void 0 ? void 0 : TerminalProjectionSelectionSchema.parse(input.terminalProjection);
|
|
1674
|
+
if (messageEgress === void 0 && input.agentMessageContext !== void 0) {
|
|
1675
|
+
throw new Error("agentMessageContext requires messageEgress");
|
|
1676
|
+
}
|
|
1677
|
+
const agentMessageContext = messageEgress === void 0 ? void 0 : AgentMessageServerContextSchema.parse(input.agentMessageContext);
|
|
1630
1678
|
const dispatchSelection = input.dispatchSelection === void 0 ? void 0 : DispatchSelectionSchema.parse(input.dispatchSelection);
|
|
1631
1679
|
const requiredToolsets = input.requiredToolsets === void 0 ? void 0 : RequiredToolsetsSchema.parse(input.requiredToolsets);
|
|
1632
1680
|
const deviceId = input.deviceId ?? this.pickFirstConnectedDevice(requiredToolsets, agentRef !== void 0);
|
|
@@ -1636,6 +1684,9 @@ var ConnectionHub = class {
|
|
|
1636
1684
|
if (egressPolicy !== void 0 && agentRef === void 0) {
|
|
1637
1685
|
throw new Error("Agent egress policy requires an explicit AgentRef; legacy task dispatch cannot consume it");
|
|
1638
1686
|
}
|
|
1687
|
+
if (messageEgress !== void 0 && egressPolicy === void 0) {
|
|
1688
|
+
throw new Error("Agent message egress requires the typed Agent egress offer path");
|
|
1689
|
+
}
|
|
1639
1690
|
if (!deviceId || !this.connections.get(deviceId)?.connected) {
|
|
1640
1691
|
throw new Error(
|
|
1641
1692
|
deviceId ? `device ${deviceId} is not connected` : "no connected device to dispatch to (M0 does not queue tasks until a device connects)"
|
|
@@ -1661,6 +1712,12 @@ var ConnectionHub = class {
|
|
|
1661
1712
|
freshAgentEgress ? `device ${deviceId} did not advertise Agent egress policy, reliable acknowledgement, and fresh-session capabilities; refusing before enqueue` : `device ${deviceId} did not advertise Agent egress policy and reliable acknowledgement capabilities; refusing before enqueue`
|
|
1662
1713
|
);
|
|
1663
1714
|
}
|
|
1715
|
+
if (messageEgress !== void 0 && !this.hasDeviceCapabilities(deviceId, [AGENT_MESSAGE_EGRESS_CAPABILITY])) {
|
|
1716
|
+
throw new Error(`device ${deviceId} did not advertise Agent message egress capability; refusing before enqueue`);
|
|
1717
|
+
}
|
|
1718
|
+
if (terminalProjection !== void 0 && !this.hasDeviceCapabilities(deviceId, [TERMINAL_PROJECTION_SELECTION_CAPABILITY])) {
|
|
1719
|
+
throw new Error(`device ${deviceId} did not advertise terminal projection selection capability; refusing before enqueue`);
|
|
1720
|
+
}
|
|
1664
1721
|
if (dispatchSelection !== void 0 && !(this.getDeviceCapabilities(deviceId)?.includes("dispatch-selection") ?? false)) {
|
|
1665
1722
|
throw new Error(
|
|
1666
1723
|
`device ${deviceId} did not advertise dispatch-selection capability; refusing authoritative provider/model dispatch`
|
|
@@ -1710,6 +1767,9 @@ var ConnectionHub = class {
|
|
|
1710
1767
|
resolveResult = resolve;
|
|
1711
1768
|
});
|
|
1712
1769
|
this.runtimes.set(taskId, { queue, resolveResult, result });
|
|
1770
|
+
if (messageEgress !== void 0 && agentRef !== void 0) {
|
|
1771
|
+
this.agentMessageRequirements.set(taskId, { deviceId, agentRef, sessionRef, requirement: messageEgress, context: agentMessageContext });
|
|
1772
|
+
}
|
|
1713
1773
|
queue.push({ kind: "state", state: record.state, at: record.createdAt });
|
|
1714
1774
|
this.serverEvents.push({ kind: "task.created", taskId, at: record.createdAt });
|
|
1715
1775
|
const commonOffer = {
|
|
@@ -1727,6 +1787,8 @@ var ConnectionHub = class {
|
|
|
1727
1787
|
dispatchSelection,
|
|
1728
1788
|
agentRef,
|
|
1729
1789
|
egressPolicy,
|
|
1790
|
+
terminalProjection,
|
|
1791
|
+
...messageEgress === void 0 ? {} : { messageEgress },
|
|
1730
1792
|
...requiredToolsets === void 0 ? {} : { requiredToolsets }
|
|
1731
1793
|
};
|
|
1732
1794
|
this.sendToDevice(
|
|
@@ -1744,6 +1806,8 @@ var ConnectionHub = class {
|
|
|
1744
1806
|
sessionRef,
|
|
1745
1807
|
agentRef,
|
|
1746
1808
|
egressPolicy,
|
|
1809
|
+
terminalProjection,
|
|
1810
|
+
...messageEgress === void 0 ? {} : { messageEgress },
|
|
1747
1811
|
...requiredToolsets === void 0 ? {} : { requiredToolsets }
|
|
1748
1812
|
},
|
|
1749
1813
|
{ taskId, sessionRef }
|
|
@@ -1752,7 +1816,12 @@ var ConnectionHub = class {
|
|
|
1752
1816
|
this.sendToDevice(
|
|
1753
1817
|
deviceId,
|
|
1754
1818
|
"task.offer_for_agent",
|
|
1755
|
-
{
|
|
1819
|
+
{
|
|
1820
|
+
...commonOffer,
|
|
1821
|
+
agentRef,
|
|
1822
|
+
...terminalProjection === void 0 ? {} : { terminalProjection },
|
|
1823
|
+
...requiredToolsets === void 0 ? {} : { requiredToolsets }
|
|
1824
|
+
},
|
|
1756
1825
|
{ taskId, sessionRef }
|
|
1757
1826
|
);
|
|
1758
1827
|
} else if (requiredToolsets === void 0) {
|
|
@@ -2045,7 +2114,7 @@ var ConnectionHub = class {
|
|
|
2045
2114
|
const envelope = createEnvelope(type, payload, combinedOpts);
|
|
2046
2115
|
const taskId = opts.taskId;
|
|
2047
2116
|
const redeliverThroughTerminal = type === "task.cancel" || type === "task.reject";
|
|
2048
|
-
const redeliverWithoutTask = type === "agent.egress.ack" || type === "agent.content.read" || type === "agent.home.projection";
|
|
2117
|
+
const redeliverWithoutTask = type === "agent.egress.ack" || type === "agent.content.read" || type === "agent.home.projection" || type === "agent.message.disposition";
|
|
2049
2118
|
outbox.ring.push({ seq, taskId, envelope, redeliverThroughTerminal, redeliverWithoutTask });
|
|
2050
2119
|
if (outbox.ring.length > OUTBOX_RING_CAPACITY) outbox.ring.shift();
|
|
2051
2120
|
this.deliverToDevice(deviceId, envelope);
|
|
@@ -3151,7 +3220,7 @@ function createByokServer(opts) {
|
|
|
3151
3220
|
const taskLeaseMs = opts.taskLeaseMs ?? DEFAULT_TASK_LEASE_MS;
|
|
3152
3221
|
const rateLimiter = new RateLimiter(opts.rateLimit);
|
|
3153
3222
|
const taskStore = opts.taskStore ?? new InMemoryTaskStore();
|
|
3154
|
-
const hub = new ConnectionHub(taskStore, devices, taskLeaseMs, rateLimiter);
|
|
3223
|
+
const hub = new ConnectionHub(taskStore, devices, taskLeaseMs, rateLimiter, opts.agentMessage);
|
|
3155
3224
|
const hono = buildHonoApp({
|
|
3156
3225
|
pairing,
|
|
3157
3226
|
devices,
|