@masons/runtime-broker 0.1.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/LICENSE +21 -0
- package/README.md +35 -0
- package/dist/broker/broker-daemon.d.ts +71 -0
- package/dist/broker/broker-daemon.d.ts.map +1 -0
- package/dist/broker/broker-daemon.js +837 -0
- package/dist/broker/claude-code-spawn-driver.d.ts +14 -0
- package/dist/broker/claude-code-spawn-driver.d.ts.map +1 -0
- package/dist/broker/claude-code-spawn-driver.js +39 -0
- package/dist/broker/closed-endpoint-lookup.d.ts +25 -0
- package/dist/broker/closed-endpoint-lookup.d.ts.map +1 -0
- package/dist/broker/closed-endpoint-lookup.js +59 -0
- package/dist/broker/codex-spawn-driver-stub.d.ts +7 -0
- package/dist/broker/codex-spawn-driver-stub.d.ts.map +1 -0
- package/dist/broker/codex-spawn-driver-stub.js +13 -0
- package/dist/broker/connector-ws.d.ts +47 -0
- package/dist/broker/connector-ws.d.ts.map +1 -0
- package/dist/broker/connector-ws.js +60 -0
- package/dist/broker/control-event-dispatcher.d.ts +21 -0
- package/dist/broker/control-event-dispatcher.d.ts.map +1 -0
- package/dist/broker/control-event-dispatcher.js +45 -0
- package/dist/broker/control-event-types.d.ts +28 -0
- package/dist/broker/control-event-types.d.ts.map +1 -0
- package/dist/broker/control-event-types.js +1 -0
- package/dist/broker/correlation-ring.d.ts +10 -0
- package/dist/broker/correlation-ring.d.ts.map +1 -0
- package/dist/broker/correlation-ring.js +32 -0
- package/dist/broker/discovery-file.d.ts +12 -0
- package/dist/broker/discovery-file.d.ts.map +1 -0
- package/dist/broker/discovery-file.js +77 -0
- package/dist/broker/endpoint-registry.d.ts +53 -0
- package/dist/broker/endpoint-registry.d.ts.map +1 -0
- package/dist/broker/endpoint-registry.js +83 -0
- package/dist/broker/endpoint-state-machine.d.ts +40 -0
- package/dist/broker/endpoint-state-machine.d.ts.map +1 -0
- package/dist/broker/endpoint-state-machine.js +92 -0
- package/dist/broker/entry.d.ts +13 -0
- package/dist/broker/entry.d.ts.map +1 -0
- package/dist/broker/entry.js +235 -0
- package/dist/broker/grace-timer.d.ts +9 -0
- package/dist/broker/grace-timer.d.ts.map +1 -0
- package/dist/broker/grace-timer.js +34 -0
- package/dist/broker/ipc-server.d.ts +79 -0
- package/dist/broker/ipc-server.d.ts.map +1 -0
- package/dist/broker/ipc-server.js +263 -0
- package/dist/broker/logger.d.ts +10 -0
- package/dist/broker/logger.d.ts.map +1 -0
- package/dist/broker/logger.js +34 -0
- package/dist/broker/network-presence-changed-event-types.d.ts +8 -0
- package/dist/broker/network-presence-changed-event-types.d.ts.map +1 -0
- package/dist/broker/network-presence-changed-event-types.js +1 -0
- package/dist/broker/network-presence-emitter.d.ts +22 -0
- package/dist/broker/network-presence-emitter.d.ts.map +1 -0
- package/dist/broker/network-presence-emitter.js +150 -0
- package/dist/broker/network-presence.d.ts +31 -0
- package/dist/broker/network-presence.d.ts.map +1 -0
- package/dist/broker/network-presence.js +109 -0
- package/dist/broker/paths.d.ts +11 -0
- package/dist/broker/paths.d.ts.map +1 -0
- package/dist/broker/paths.js +30 -0
- package/dist/broker/plugin-liveness.d.ts +2 -0
- package/dist/broker/plugin-liveness.d.ts.map +1 -0
- package/dist/broker/plugin-liveness.js +15 -0
- package/dist/broker/received-message-correlation-cache.d.ts +23 -0
- package/dist/broker/received-message-correlation-cache.d.ts.map +1 -0
- package/dist/broker/received-message-correlation-cache.js +114 -0
- package/dist/broker/reconnecting-buffer.d.ts +23 -0
- package/dist/broker/reconnecting-buffer.d.ts.map +1 -0
- package/dist/broker/reconnecting-buffer.js +107 -0
- package/dist/broker/routing-table.d.ts +22 -0
- package/dist/broker/routing-table.d.ts.map +1 -0
- package/dist/broker/routing-table.js +35 -0
- package/dist/broker/runtime-endpoint-port.d.ts +20 -0
- package/dist/broker/runtime-endpoint-port.d.ts.map +1 -0
- package/dist/broker/runtime-endpoint-port.js +1 -0
- package/dist/broker/services-event-client.d.ts +21 -0
- package/dist/broker/services-event-client.d.ts.map +1 -0
- package/dist/broker/services-event-client.js +221 -0
- package/dist/broker/spawn-correlation.d.ts +28 -0
- package/dist/broker/spawn-correlation.d.ts.map +1 -0
- package/dist/broker/spawn-correlation.js +77 -0
- package/dist/broker/spawn-driver.d.ts +27 -0
- package/dist/broker/spawn-driver.d.ts.map +1 -0
- package/dist/broker/spawn-driver.js +15 -0
- package/dist/broker/task-hint-handler.d.ts +21 -0
- package/dist/broker/task-hint-handler.d.ts.map +1 -0
- package/dist/broker/task-hint-handler.js +33 -0
- package/dist/broker/transition-state-retry-queue.d.ts +20 -0
- package/dist/broker/transition-state-retry-queue.d.ts.map +1 -0
- package/dist/broker/transition-state-retry-queue.js +48 -0
- package/dist/broker/undispatched-changed-event-types.d.ts +29 -0
- package/dist/broker/undispatched-changed-event-types.d.ts.map +1 -0
- package/dist/broker/undispatched-changed-event-types.js +14 -0
- package/dist/broker/undispatched-emitter.d.ts +22 -0
- package/dist/broker/undispatched-emitter.d.ts.map +1 -0
- package/dist/broker/undispatched-emitter.js +149 -0
- package/dist/broker/undispatched-inbox.d.ts +26 -0
- package/dist/broker/undispatched-inbox.d.ts.map +1 -0
- package/dist/broker/undispatched-inbox.js +53 -0
- package/dist/broker/version-handshake.d.ts +30 -0
- package/dist/broker/version-handshake.d.ts.map +1 -0
- package/dist/broker/version-handshake.js +47 -0
- package/dist/broker-client/broker-client.d.ts +65 -0
- package/dist/broker-client/broker-client.d.ts.map +1 -0
- package/dist/broker-client/broker-client.js +165 -0
- package/dist/broker-client/lazy-spawn.d.ts +18 -0
- package/dist/broker-client/lazy-spawn.d.ts.map +1 -0
- package/dist/broker-client/lazy-spawn.js +61 -0
- package/dist/config-fs.d.ts +4 -0
- package/dist/config-fs.d.ts.map +1 -0
- package/dist/config-fs.js +23 -0
- package/dist/connector-client.d.ts +65 -0
- package/dist/connector-client.d.ts.map +1 -0
- package/dist/connector-client.js +364 -0
- package/dist/environment-context.d.ts +21 -0
- package/dist/environment-context.d.ts.map +1 -0
- package/dist/environment-context.js +39 -0
- package/dist/platform-client.d.ts +84 -0
- package/dist/platform-client.d.ts.map +1 -0
- package/dist/platform-client.js +94 -0
- package/dist/runtime-endpoint-client.d.ts +74 -0
- package/dist/runtime-endpoint-client.d.ts.map +1 -0
- package/dist/runtime-endpoint-client.js +163 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +38 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import envPaths from "env-paths";
|
|
4
|
+
const AGENT_IDENTITY_HEX_LEN = 16;
|
|
5
|
+
const ENV_PATHS_NAME = "masons-runtime-broker";
|
|
6
|
+
export function resolveBrokerPaths(principal, runtimeToken, opts = {}) {
|
|
7
|
+
if (!principal) {
|
|
8
|
+
throw new Error("principal must be a non-empty string");
|
|
9
|
+
}
|
|
10
|
+
if (!runtimeToken) {
|
|
11
|
+
throw new Error("runtimeToken must be a non-empty string");
|
|
12
|
+
}
|
|
13
|
+
const userData = opts.userDataDir ?? envPaths(ENV_PATHS_NAME, { suffix: "" }).data;
|
|
14
|
+
const agentIdentity = createHash("sha256")
|
|
15
|
+
.update(runtimeToken, "utf8")
|
|
16
|
+
.digest("hex")
|
|
17
|
+
.slice(0, AGENT_IDENTITY_HEX_LEN);
|
|
18
|
+
const discoveryDir = join(userData, "runtime-broker", sanitizeSegment(principal), agentIdentity);
|
|
19
|
+
const logDir = join(userData, "runtime-broker", "log");
|
|
20
|
+
const buffersDir = join(discoveryDir, "buffers");
|
|
21
|
+
return {
|
|
22
|
+
discoveryFile: join(discoveryDir, "discovery.json"),
|
|
23
|
+
discoveryDir,
|
|
24
|
+
logDir,
|
|
25
|
+
buffersDir,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function sanitizeSegment(segment) {
|
|
29
|
+
return segment.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-liveness.d.ts","sourceRoot":"","sources":["../../src/broker/plugin-liveness.ts"],"names":[],"mappings":"AAyBA,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAiBzD"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function isPluginAlive(plugin_pid) {
|
|
2
|
+
if (!Number.isFinite(plugin_pid) || plugin_pid <= 0) {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
try {
|
|
6
|
+
process.kill(plugin_pid, 0);
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
const code = err.code;
|
|
11
|
+
if (code === "ESRCH")
|
|
12
|
+
return false;
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const DEFAULT_TTL_MS = 86400000;
|
|
2
|
+
export declare const DEFAULT_CAP_PER_ENDPOINT = 1000;
|
|
3
|
+
export interface ReceivedMessageCorrelationCacheOptions {
|
|
4
|
+
ttlMs?: number;
|
|
5
|
+
capPerEndpoint?: number;
|
|
6
|
+
now?: () => number;
|
|
7
|
+
}
|
|
8
|
+
export type ReplyAwareResolve = {
|
|
9
|
+
ok: true;
|
|
10
|
+
} | {
|
|
11
|
+
ok: false;
|
|
12
|
+
reason: "unknown_correlation_id" | "wrong_endpoint" | "ttl_expired";
|
|
13
|
+
};
|
|
14
|
+
export interface ReceivedMessageCorrelationCache {
|
|
15
|
+
record(endpoint_id: string, correlation_id: string): void;
|
|
16
|
+
resolve(endpoint_id: string, correlation_id: string): ReplyAwareResolve;
|
|
17
|
+
size(): number;
|
|
18
|
+
sizeForEndpoint(endpoint_id: string): number;
|
|
19
|
+
sweep(now?: number): number;
|
|
20
|
+
forgetEndpoint(endpoint_id: string): void;
|
|
21
|
+
}
|
|
22
|
+
export declare function createReceivedMessageCorrelationCache(opts?: ReceivedMessageCorrelationCacheOptions): ReceivedMessageCorrelationCache;
|
|
23
|
+
//# sourceMappingURL=received-message-correlation-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"received-message-correlation-cache.d.ts","sourceRoot":"","sources":["../../src/broker/received-message-correlation-cache.ts"],"names":[],"mappings":"AA8DA,eAAO,MAAM,cAAc,WAAa,CAAC;AAGzC,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,MAAM,WAAW,sCAAsC;IAErD,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,wBAAwB,GAAG,gBAAgB,GAAG,aAAa,CAAC;CACrE,CAAC;AAMN,MAAM,WAAW,+BAA+B;IAa9C,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAQ1D,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,iBAAiB,CAAC;IAGxE,IAAI,IAAI,MAAM,CAAC;IAGf,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAQ7C,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAM5B,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED,wBAAgB,qCAAqC,CACnD,IAAI,GAAE,sCAA2C,GAChD,+BAA+B,CAgIjC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export const DEFAULT_TTL_MS = 86_400_000;
|
|
2
|
+
export const DEFAULT_CAP_PER_ENDPOINT = 1000;
|
|
3
|
+
export function createReceivedMessageCorrelationCache(opts = {}) {
|
|
4
|
+
const ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
5
|
+
const cap = opts.capPerEndpoint ?? DEFAULT_CAP_PER_ENDPOINT;
|
|
6
|
+
const clock = opts.now ?? Date.now;
|
|
7
|
+
if (ttlMs <= 0)
|
|
8
|
+
throw new Error(`ttlMs must be positive: ${ttlMs}`);
|
|
9
|
+
if (cap <= 0)
|
|
10
|
+
throw new Error(`capPerEndpoint must be positive: ${cap}`);
|
|
11
|
+
const perEndpoint = new Map();
|
|
12
|
+
const correlationOwner = new Map();
|
|
13
|
+
const evict = (endpoint_id, correlation_id) => {
|
|
14
|
+
const inner = perEndpoint.get(endpoint_id);
|
|
15
|
+
if (!inner)
|
|
16
|
+
return;
|
|
17
|
+
inner.delete(correlation_id);
|
|
18
|
+
if (inner.size === 0)
|
|
19
|
+
perEndpoint.delete(endpoint_id);
|
|
20
|
+
if (correlationOwner.get(correlation_id) === endpoint_id) {
|
|
21
|
+
correlationOwner.delete(correlation_id);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
record(endpoint_id, correlation_id) {
|
|
26
|
+
const priorOwner = correlationOwner.get(correlation_id);
|
|
27
|
+
if (priorOwner && priorOwner !== endpoint_id) {
|
|
28
|
+
const priorInner = perEndpoint.get(priorOwner);
|
|
29
|
+
priorInner?.delete(correlation_id);
|
|
30
|
+
if (priorInner && priorInner.size === 0) {
|
|
31
|
+
perEndpoint.delete(priorOwner);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let inner = perEndpoint.get(endpoint_id);
|
|
35
|
+
if (!inner) {
|
|
36
|
+
inner = new Map();
|
|
37
|
+
perEndpoint.set(endpoint_id, inner);
|
|
38
|
+
}
|
|
39
|
+
inner.delete(correlation_id);
|
|
40
|
+
inner.set(correlation_id, { received_at: clock() });
|
|
41
|
+
correlationOwner.set(correlation_id, endpoint_id);
|
|
42
|
+
while (inner.size > cap) {
|
|
43
|
+
const oldestKey = inner.keys().next().value;
|
|
44
|
+
if (oldestKey === undefined)
|
|
45
|
+
break;
|
|
46
|
+
inner.delete(oldestKey);
|
|
47
|
+
if (correlationOwner.get(oldestKey) === endpoint_id) {
|
|
48
|
+
correlationOwner.delete(oldestKey);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
resolve(endpoint_id, correlation_id) {
|
|
53
|
+
const owner = correlationOwner.get(correlation_id);
|
|
54
|
+
if (!owner) {
|
|
55
|
+
return { ok: false, reason: "unknown_correlation_id" };
|
|
56
|
+
}
|
|
57
|
+
if (owner !== endpoint_id) {
|
|
58
|
+
return { ok: false, reason: "wrong_endpoint" };
|
|
59
|
+
}
|
|
60
|
+
const inner = perEndpoint.get(endpoint_id);
|
|
61
|
+
const entry = inner?.get(correlation_id);
|
|
62
|
+
if (!entry) {
|
|
63
|
+
correlationOwner.delete(correlation_id);
|
|
64
|
+
return { ok: false, reason: "unknown_correlation_id" };
|
|
65
|
+
}
|
|
66
|
+
if (clock() - entry.received_at >= ttlMs) {
|
|
67
|
+
evict(endpoint_id, correlation_id);
|
|
68
|
+
return { ok: false, reason: "ttl_expired" };
|
|
69
|
+
}
|
|
70
|
+
return { ok: true };
|
|
71
|
+
},
|
|
72
|
+
size() {
|
|
73
|
+
let total = 0;
|
|
74
|
+
for (const inner of perEndpoint.values())
|
|
75
|
+
total += inner.size;
|
|
76
|
+
return total;
|
|
77
|
+
},
|
|
78
|
+
sizeForEndpoint(endpoint_id) {
|
|
79
|
+
return perEndpoint.get(endpoint_id)?.size ?? 0;
|
|
80
|
+
},
|
|
81
|
+
sweep(now = clock()) {
|
|
82
|
+
let evicted = 0;
|
|
83
|
+
for (const [endpoint_id, inner] of perEndpoint) {
|
|
84
|
+
const expired = [];
|
|
85
|
+
for (const [correlation_id, entry] of inner) {
|
|
86
|
+
if (now - entry.received_at >= ttlMs) {
|
|
87
|
+
expired.push(correlation_id);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const correlation_id of expired) {
|
|
91
|
+
inner.delete(correlation_id);
|
|
92
|
+
if (correlationOwner.get(correlation_id) === endpoint_id) {
|
|
93
|
+
correlationOwner.delete(correlation_id);
|
|
94
|
+
}
|
|
95
|
+
evicted++;
|
|
96
|
+
}
|
|
97
|
+
if (inner.size === 0)
|
|
98
|
+
perEndpoint.delete(endpoint_id);
|
|
99
|
+
}
|
|
100
|
+
return evicted;
|
|
101
|
+
},
|
|
102
|
+
forgetEndpoint(endpoint_id) {
|
|
103
|
+
const inner = perEndpoint.get(endpoint_id);
|
|
104
|
+
if (!inner)
|
|
105
|
+
return;
|
|
106
|
+
for (const correlation_id of inner.keys()) {
|
|
107
|
+
if (correlationOwner.get(correlation_id) === endpoint_id) {
|
|
108
|
+
correlationOwner.delete(correlation_id);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
perEndpoint.delete(endpoint_id);
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface BufferedMessage {
|
|
2
|
+
id: string;
|
|
3
|
+
arrived_at: string;
|
|
4
|
+
envelope: {
|
|
5
|
+
from: string;
|
|
6
|
+
content: string;
|
|
7
|
+
contentType: string;
|
|
8
|
+
metadata?: Record<string, unknown>;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export interface ReconnectingBuffer {
|
|
12
|
+
append(msg: BufferedMessage): void;
|
|
13
|
+
read(): BufferedMessage[];
|
|
14
|
+
drain(): BufferedMessage[];
|
|
15
|
+
delete(): void;
|
|
16
|
+
size(): number;
|
|
17
|
+
}
|
|
18
|
+
export interface ReconnectingBufferManager {
|
|
19
|
+
forEndpoint(endpoint_id: string): ReconnectingBuffer;
|
|
20
|
+
cleanup(endpoint_id: string): void;
|
|
21
|
+
}
|
|
22
|
+
export declare function createReconnectingBufferManager(buffersDir: string): ReconnectingBufferManager;
|
|
23
|
+
//# sourceMappingURL=reconnecting-buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconnecting-buffer.d.ts","sourceRoot":"","sources":["../../src/broker/reconnecting-buffer.ts"],"names":[],"mappings":"AA+CA,MAAM,WAAW,eAAe;IAE9B,EAAE,EAAE,MAAM,CAAC;IAEX,UAAU,EAAE,MAAM,CAAC;IAEnB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IAEjC,MAAM,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CAAC;IAEnC,IAAI,IAAI,eAAe,EAAE,CAAC;IAE1B,KAAK,IAAI,eAAe,EAAE,CAAC;IAE3B,MAAM,IAAI,IAAI,CAAC;IAEf,IAAI,IAAI,MAAM,CAAC;CAChB;AA8ED,MAAM,WAAW,yBAAyB;IAExC,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB,CAAC;IAErD,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,MAAM,GACjB,yBAAyB,CAmB3B"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { closeSync, fdatasyncSync, mkdirSync, openSync, readFileSync, unlinkSync, writeSync, } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
class FileBackedBuffer {
|
|
4
|
+
path;
|
|
5
|
+
cached = null;
|
|
6
|
+
constructor(path) {
|
|
7
|
+
this.path = path;
|
|
8
|
+
}
|
|
9
|
+
append(msg) {
|
|
10
|
+
const line = `${JSON.stringify(msg)}\n`;
|
|
11
|
+
try {
|
|
12
|
+
mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
}
|
|
16
|
+
const fd = openSync(this.path, "a", 0o600);
|
|
17
|
+
try {
|
|
18
|
+
writeSync(fd, line);
|
|
19
|
+
fdatasyncSync(fd);
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
closeSync(fd);
|
|
23
|
+
}
|
|
24
|
+
this.cached = null;
|
|
25
|
+
}
|
|
26
|
+
read() {
|
|
27
|
+
if (this.cached !== null)
|
|
28
|
+
return [...this.cached];
|
|
29
|
+
let raw;
|
|
30
|
+
try {
|
|
31
|
+
raw = readFileSync(this.path, "utf8");
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
if (isNodeErr(err, "ENOENT")) {
|
|
35
|
+
this.cached = [];
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
const lines = raw.split("\n").filter((l) => l.length > 0);
|
|
41
|
+
const parsed = [];
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
try {
|
|
44
|
+
parsed.push(JSON.parse(line));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
this.cached = parsed;
|
|
50
|
+
return [...parsed];
|
|
51
|
+
}
|
|
52
|
+
drain() {
|
|
53
|
+
const out = this.read();
|
|
54
|
+
this.delete();
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
delete() {
|
|
58
|
+
try {
|
|
59
|
+
unlinkSync(this.path);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
if (isNodeErr(err, "ENOENT")) {
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
this.cached = [];
|
|
69
|
+
}
|
|
70
|
+
size() {
|
|
71
|
+
if (this.cached !== null)
|
|
72
|
+
return this.cached.length;
|
|
73
|
+
return this.read().length;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function createReconnectingBufferManager(buffersDir) {
|
|
77
|
+
const cache = new Map();
|
|
78
|
+
return {
|
|
79
|
+
forEndpoint(endpoint_id) {
|
|
80
|
+
let buf = cache.get(endpoint_id);
|
|
81
|
+
if (!buf) {
|
|
82
|
+
buf = new FileBackedBuffer(join(buffersDir, `${sanitizeId(endpoint_id)}.ndjson`));
|
|
83
|
+
cache.set(endpoint_id, buf);
|
|
84
|
+
}
|
|
85
|
+
return buf;
|
|
86
|
+
},
|
|
87
|
+
cleanup(endpoint_id) {
|
|
88
|
+
const buf = cache.get(endpoint_id);
|
|
89
|
+
if (buf)
|
|
90
|
+
buf.delete();
|
|
91
|
+
cache.delete(endpoint_id);
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function sanitizeId(id) {
|
|
96
|
+
return id.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
97
|
+
}
|
|
98
|
+
function dirname(p) {
|
|
99
|
+
const idx = p.lastIndexOf("/");
|
|
100
|
+
return idx >= 0 ? p.slice(0, idx) : ".";
|
|
101
|
+
}
|
|
102
|
+
function isNodeErr(err, code) {
|
|
103
|
+
return (typeof err === "object" &&
|
|
104
|
+
err !== null &&
|
|
105
|
+
"code" in err &&
|
|
106
|
+
err.code === code);
|
|
107
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { BrokerEndpointEntry, EndpointRegistry } from "./endpoint-registry.js";
|
|
2
|
+
export interface InboundMessageMeta {
|
|
3
|
+
target_endpoint_id?: string;
|
|
4
|
+
correlation_id?: string;
|
|
5
|
+
}
|
|
6
|
+
export type RouteResult = {
|
|
7
|
+
kind: "endpoint";
|
|
8
|
+
entry: BrokerEndpointEntry;
|
|
9
|
+
via: "target_endpoint_id" | "correlation_id";
|
|
10
|
+
} | {
|
|
11
|
+
kind: "no_match";
|
|
12
|
+
};
|
|
13
|
+
export interface RoutingTableOptions {
|
|
14
|
+
onCollision?: (correlation_id: string, count: number) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare class RoutingTable {
|
|
17
|
+
private readonly registry;
|
|
18
|
+
private readonly opts;
|
|
19
|
+
constructor(registry: EndpointRegistry, opts?: RoutingTableOptions);
|
|
20
|
+
route(meta: InboundMessageMeta): RouteResult;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=routing-table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing-table.d.ts","sourceRoot":"","sources":["../../src/broker/routing-table.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,kBAAkB;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,WAAW,GACnB;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,mBAAmB,CAAC;IAC3B,GAAG,EAAE,oBAAoB,GAAG,gBAAgB,CAAC;CAC9C,GACD;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzB,MAAM,WAAW,mBAAmB;IAElC,WAAW,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/D;AAED,qBAAa,YAAY;IAErB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADJ,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,GAAE,mBAAwB;IASjD,KAAK,CAAC,IAAI,EAAE,kBAAkB,GAAG,WAAW;CAmC7C"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export class RoutingTable {
|
|
2
|
+
registry;
|
|
3
|
+
opts;
|
|
4
|
+
constructor(registry, opts = {}) {
|
|
5
|
+
this.registry = registry;
|
|
6
|
+
this.opts = opts;
|
|
7
|
+
}
|
|
8
|
+
route(meta) {
|
|
9
|
+
if (meta.target_endpoint_id) {
|
|
10
|
+
const entry = this.registry.get(meta.target_endpoint_id);
|
|
11
|
+
if (entry) {
|
|
12
|
+
return { kind: "endpoint", entry, via: "target_endpoint_id" };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (meta.correlation_id) {
|
|
16
|
+
let matched;
|
|
17
|
+
let collisionCount = 0;
|
|
18
|
+
for (const entry of this.registry.list()) {
|
|
19
|
+
if (entry.correlation_ring.has(meta.correlation_id)) {
|
|
20
|
+
collisionCount++;
|
|
21
|
+
if (!matched) {
|
|
22
|
+
matched = entry;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (collisionCount > 1 && this.opts.onCollision) {
|
|
27
|
+
this.opts.onCollision(meta.correlation_id, collisionCount);
|
|
28
|
+
}
|
|
29
|
+
if (matched) {
|
|
30
|
+
return { kind: "endpoint", entry: matched, via: "correlation_id" };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return { kind: "no_match" };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RegisterRuntimeEndpointParams, RegisterRuntimeEndpointResponse, TransitionRuntimeEndpointStateParams } from "../runtime-endpoint-client.js";
|
|
2
|
+
import type { NetworkPresenceChangedEvent } from "./network-presence-changed-event-types.js";
|
|
3
|
+
import type { UndispatchedChangedEvent } from "./undispatched-changed-event-types.js";
|
|
4
|
+
export type EmitOutcome = {
|
|
5
|
+
ok: true;
|
|
6
|
+
} | {
|
|
7
|
+
ok: false;
|
|
8
|
+
terminal: boolean;
|
|
9
|
+
status?: number;
|
|
10
|
+
detail?: string;
|
|
11
|
+
};
|
|
12
|
+
export interface RuntimeEndpointPort {
|
|
13
|
+
register(params: RegisterRuntimeEndpointParams): Promise<RegisterRuntimeEndpointResponse>;
|
|
14
|
+
heartbeat(endpointId: string): Promise<void>;
|
|
15
|
+
unregister(endpointId: string, asNodeId?: string): Promise<void>;
|
|
16
|
+
transitionState(endpointId: string, params: TransitionRuntimeEndpointStateParams): Promise<void>;
|
|
17
|
+
emitUndispatchedChanged?(event: UndispatchedChangedEvent): Promise<EmitOutcome>;
|
|
18
|
+
emitNetworkPresenceChanged?(event: NetworkPresenceChangedEvent): Promise<EmitOutcome>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=runtime-endpoint-port.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-endpoint-port.d.ts","sourceRoot":"","sources":["../../src/broker/runtime-endpoint-port.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACV,6BAA6B,EAC7B,+BAA+B,EAC/B,oCAAoC,EACrC,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2CAA2C,CAAC;AAC7F,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AAWtF,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CACN,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,+BAA+B,CAAC,CAAC;IAC5C,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAO7C,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAQjE,eAAe,CACb,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,oCAAoC,GAC3C,OAAO,CAAC,IAAI,CAAC,CAAC;IAqBjB,uBAAuB,CAAC,CACtB,KAAK,EAAE,wBAAwB,GAC9B,OAAO,CAAC,WAAW,CAAC,CAAC;IAYxB,0BAA0B,CAAC,CACzB,KAAK,EAAE,2BAA2B,GACjC,OAAO,CAAC,WAAW,CAAC,CAAC;CACzB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ControlEventDispatcher } from "./control-event-dispatcher.js";
|
|
2
|
+
import type { BrokerLogger } from "./logger.js";
|
|
3
|
+
export declare const DEFAULT_BACKOFF_INITIAL_MS = 1000;
|
|
4
|
+
export declare const DEFAULT_BACKOFF_MAX_MS = 30000;
|
|
5
|
+
export interface ServicesEventClientOptions {
|
|
6
|
+
apiHost: string;
|
|
7
|
+
runtimeKey: string;
|
|
8
|
+
agentId: string;
|
|
9
|
+
dispatcher: ControlEventDispatcher;
|
|
10
|
+
logger: BrokerLogger;
|
|
11
|
+
fetchImpl?: typeof globalThis.fetch;
|
|
12
|
+
backoffInitialMs?: number;
|
|
13
|
+
backoffMaxMs?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface ServicesEventClient {
|
|
16
|
+
start(): Promise<void>;
|
|
17
|
+
stop(): Promise<void>;
|
|
18
|
+
lastSeenKey(): string | undefined;
|
|
19
|
+
}
|
|
20
|
+
export declare function createServicesEventClient(opts: ServicesEventClientOptions): ServicesEventClient;
|
|
21
|
+
//# sourceMappingURL=services-event-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"services-event-client.d.ts","sourceRoot":"","sources":["../../src/broker/services-event-client.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EACV,sBAAsB,EAEvB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,eAAO,MAAM,0BAA0B,OAAQ,CAAC;AAChD,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C,MAAM,WAAW,0BAA0B;IAEzC,OAAO,EAAE,MAAM,CAAC;IAEhB,UAAU,EAAE,MAAM,CAAC;IAEnB,OAAO,EAAE,MAAM,CAAC;IAEhB,UAAU,EAAE,sBAAsB,CAAC;IAEnC,MAAM,EAAE,YAAY,CAAC;IAErB,SAAS,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAEpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IASlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtB,WAAW,IAAI,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,0BAA0B,GAC/B,mBAAmB,CAuMrB"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
export const DEFAULT_BACKOFF_INITIAL_MS = 1_000;
|
|
2
|
+
export const DEFAULT_BACKOFF_MAX_MS = 30_000;
|
|
3
|
+
export function createServicesEventClient(opts) {
|
|
4
|
+
const { dispatcher, logger } = opts;
|
|
5
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
6
|
+
const backoffInitial = opts.backoffInitialMs ?? DEFAULT_BACKOFF_INITIAL_MS;
|
|
7
|
+
const backoffMax = opts.backoffMaxMs ?? DEFAULT_BACKOFF_MAX_MS;
|
|
8
|
+
let stopped = false;
|
|
9
|
+
let lastEventId;
|
|
10
|
+
let currentAbort = null;
|
|
11
|
+
let backoffMs = backoffInitial;
|
|
12
|
+
let loopPromise = null;
|
|
13
|
+
let started = false;
|
|
14
|
+
const baseUrl = makeBaseUrl(opts.apiHost);
|
|
15
|
+
const subscribeOnce = async () => {
|
|
16
|
+
const ctrl = new AbortController();
|
|
17
|
+
currentAbort = ctrl;
|
|
18
|
+
const url = new URL(`${baseUrl}/runtime/control-events`);
|
|
19
|
+
url.searchParams.set("agent_id", opts.agentId);
|
|
20
|
+
const headers = {
|
|
21
|
+
Authorization: `Bearer ${opts.runtimeKey}`,
|
|
22
|
+
Accept: "text/event-stream",
|
|
23
|
+
};
|
|
24
|
+
if (lastEventId)
|
|
25
|
+
headers["Last-Event-ID"] = lastEventId;
|
|
26
|
+
const res = await fetchImpl(url.toString(), {
|
|
27
|
+
method: "GET",
|
|
28
|
+
headers,
|
|
29
|
+
signal: ctrl.signal,
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok || !res.body) {
|
|
32
|
+
throw new Error(`SSE subscribe failed: ${res.status}`);
|
|
33
|
+
}
|
|
34
|
+
backoffMs = backoffInitial;
|
|
35
|
+
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
36
|
+
let buffer = "";
|
|
37
|
+
while (true) {
|
|
38
|
+
const { done, value } = await reader.read();
|
|
39
|
+
if (done)
|
|
40
|
+
break;
|
|
41
|
+
buffer += value.replace(/\r\n?/g, "\n");
|
|
42
|
+
let sep = buffer.indexOf("\n\n");
|
|
43
|
+
while (sep >= 0) {
|
|
44
|
+
const frame = buffer.slice(0, sep);
|
|
45
|
+
buffer = buffer.slice(sep + 2);
|
|
46
|
+
await handleFrame(frame).catch((err) => {
|
|
47
|
+
logger.warn("sse_frame_error", {
|
|
48
|
+
err: err instanceof Error ? err.message : String(err),
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
sep = buffer.indexOf("\n\n");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const handleFrame = async (frame) => {
|
|
56
|
+
let id;
|
|
57
|
+
const dataLines = [];
|
|
58
|
+
for (const line of frame.split("\n")) {
|
|
59
|
+
if (line === "" || line.startsWith(":"))
|
|
60
|
+
continue;
|
|
61
|
+
const colon = line.indexOf(":");
|
|
62
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
63
|
+
let value = colon === -1 ? "" : line.slice(colon + 1);
|
|
64
|
+
if (value.startsWith(" "))
|
|
65
|
+
value = value.slice(1);
|
|
66
|
+
if (field === "id")
|
|
67
|
+
id = value;
|
|
68
|
+
else if (field === "data")
|
|
69
|
+
dataLines.push(value);
|
|
70
|
+
}
|
|
71
|
+
if (dataLines.length === 0)
|
|
72
|
+
return;
|
|
73
|
+
const data = dataLines.join("\n");
|
|
74
|
+
if (id)
|
|
75
|
+
lastEventId = id;
|
|
76
|
+
let parsed;
|
|
77
|
+
try {
|
|
78
|
+
parsed = JSON.parse(data);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
logger.warn("sse_frame_unparseable", { data: data.slice(0, 80) });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (hasIdempotencyKey(parsed) && hasUnsupportedVersion(parsed)) {
|
|
85
|
+
await postAck({
|
|
86
|
+
idempotency_key: parsed.idempotency_key,
|
|
87
|
+
status: "failed",
|
|
88
|
+
detail: "unsupported_protocol_version",
|
|
89
|
+
});
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!isControlEvent(parsed)) {
|
|
93
|
+
if (hasIdempotencyKey(parsed)) {
|
|
94
|
+
await postAck({
|
|
95
|
+
idempotency_key: parsed.idempotency_key,
|
|
96
|
+
status: "applied",
|
|
97
|
+
detail: "unknown_variant_dropped",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const outcome = await dispatcher.dispatch(parsed);
|
|
103
|
+
await postAck(outcomeToAck(parsed.idempotency_key, outcome));
|
|
104
|
+
};
|
|
105
|
+
const postAck = async (ack) => {
|
|
106
|
+
try {
|
|
107
|
+
const res = await fetchImpl(`${baseUrl}/runtime/control-ack`, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: {
|
|
110
|
+
Authorization: `Bearer ${opts.runtimeKey}`,
|
|
111
|
+
"Content-Type": "application/json",
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify(ack),
|
|
114
|
+
});
|
|
115
|
+
if (!res.ok) {
|
|
116
|
+
logger.warn("ack_post_failed", {
|
|
117
|
+
status: res.status,
|
|
118
|
+
idempotency_key: ack.idempotency_key,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
logger.warn("ack_post_error", {
|
|
124
|
+
err: err instanceof Error ? err.message : String(err),
|
|
125
|
+
idempotency_key: ack.idempotency_key,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const loop = async () => {
|
|
130
|
+
while (!stopped) {
|
|
131
|
+
try {
|
|
132
|
+
await subscribeOnce();
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
if (stopped)
|
|
136
|
+
return;
|
|
137
|
+
logger.warn("sse_reconnect", {
|
|
138
|
+
backoff_ms: backoffMs,
|
|
139
|
+
err: err instanceof Error ? err.message : String(err),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (stopped)
|
|
143
|
+
return;
|
|
144
|
+
await sleep(backoffMs);
|
|
145
|
+
backoffMs = Math.min(backoffMs * 2, backoffMax);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
async start() {
|
|
150
|
+
if (started)
|
|
151
|
+
return;
|
|
152
|
+
started = true;
|
|
153
|
+
stopped = false;
|
|
154
|
+
loopPromise = loop();
|
|
155
|
+
},
|
|
156
|
+
async stop() {
|
|
157
|
+
stopped = true;
|
|
158
|
+
if (currentAbort) {
|
|
159
|
+
try {
|
|
160
|
+
currentAbort.abort();
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (loopPromise) {
|
|
166
|
+
await loopPromise.catch(() => { });
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
lastSeenKey() {
|
|
170
|
+
return lastEventId;
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function outcomeToAck(idempotency_key, outcome) {
|
|
175
|
+
if (outcome.ok) {
|
|
176
|
+
if (outcome.ack_hint === "received") {
|
|
177
|
+
return { idempotency_key, status: "received" };
|
|
178
|
+
}
|
|
179
|
+
return { idempotency_key, status: "applied" };
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
idempotency_key,
|
|
183
|
+
status: "failed",
|
|
184
|
+
detail: outcome.detail,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function isControlEvent(value) {
|
|
188
|
+
if (typeof value !== "object" || value === null)
|
|
189
|
+
return false;
|
|
190
|
+
const v = value;
|
|
191
|
+
if (v.version !== 1)
|
|
192
|
+
return false;
|
|
193
|
+
if (typeof v.idempotency_key !== "string")
|
|
194
|
+
return false;
|
|
195
|
+
if (typeof v.emitted_at !== "number")
|
|
196
|
+
return false;
|
|
197
|
+
if (typeof v.type !== "string")
|
|
198
|
+
return false;
|
|
199
|
+
return (v.type === "dispatch_undispatched" ||
|
|
200
|
+
v.type === "spawn_request" ||
|
|
201
|
+
v.type === "force_unregister");
|
|
202
|
+
}
|
|
203
|
+
function hasIdempotencyKey(value) {
|
|
204
|
+
return (typeof value === "object" &&
|
|
205
|
+
value !== null &&
|
|
206
|
+
typeof value.idempotency_key === "string");
|
|
207
|
+
}
|
|
208
|
+
function hasUnsupportedVersion(value) {
|
|
209
|
+
if (typeof value !== "object" || value === null)
|
|
210
|
+
return false;
|
|
211
|
+
const v = value;
|
|
212
|
+
return typeof v.version === "number" && v.version !== 1;
|
|
213
|
+
}
|
|
214
|
+
function makeBaseUrl(apiHost) {
|
|
215
|
+
const trimmed = apiHost.replace(/\/+$/, "");
|
|
216
|
+
const origin = /^https?:\/\//.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
217
|
+
return `${origin}/v1`;
|
|
218
|
+
}
|
|
219
|
+
function sleep(ms) {
|
|
220
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
221
|
+
}
|