@hyperdrive.bot/paseo-client 0.2.5
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 +12 -0
- package/dist/compat/normalize-provider-models.d.ts +11 -0
- package/dist/compat/normalize-provider-models.js +42 -0
- package/dist/daemon-client-relay-e2ee-transport.d.ts +8 -0
- package/dist/daemon-client-relay-e2ee-transport.js +161 -0
- package/dist/daemon-client-runtime-metrics.d.ts +39 -0
- package/dist/daemon-client-runtime-metrics.js +173 -0
- package/dist/daemon-client-transport-types.d.ts +36 -0
- package/dist/daemon-client-transport-types.js +2 -0
- package/dist/daemon-client-transport-utils.d.ts +9 -0
- package/dist/daemon-client-transport-utils.js +121 -0
- package/dist/daemon-client-transport.d.ts +5 -0
- package/dist/daemon-client-transport.js +4 -0
- package/dist/daemon-client-websocket-transport.d.ts +8 -0
- package/dist/daemon-client-websocket-transport.js +120 -0
- package/dist/daemon-client.d.ts +1153 -0
- package/dist/daemon-client.js +4594 -0
- package/dist/index.d.ts +282 -0
- package/dist/index.js +177 -0
- package/dist/terminal-stream-router.d.ts +28 -0
- package/dist/terminal-stream-router.js +108 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# @hyperdrive.bot/paseo-client
|
|
2
|
+
|
|
3
|
+
Paseo's JavaScript/TypeScript client package.
|
|
4
|
+
|
|
5
|
+
## Stability
|
|
6
|
+
|
|
7
|
+
This package is public so Paseo's published packages can depend on it cleanly.
|
|
8
|
+
It is not a stable public SDK yet.
|
|
9
|
+
|
|
10
|
+
APIs, exports, runtime behavior, and types may change or disappear in any
|
|
11
|
+
release without advance notice. Use it outside Paseo at your own risk until the
|
|
12
|
+
package is explicitly documented as stable.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { GetProvidersSnapshotResponseMessage, ListProviderModelsResponseMessage, SessionOutboundMessage } from "@hyperdrive.bot/paseo-protocol/messages";
|
|
2
|
+
type ListProviderModelsPayload = ListProviderModelsResponseMessage["payload"];
|
|
3
|
+
type GetProvidersSnapshotPayload = GetProvidersSnapshotResponseMessage["payload"];
|
|
4
|
+
type ProvidersSnapshotUpdatePayload = Extract<SessionOutboundMessage, {
|
|
5
|
+
type: "providers_snapshot_update";
|
|
6
|
+
}>["payload"];
|
|
7
|
+
export declare function normalizeListProviderModelsPayload(payload: ListProviderModelsPayload): ListProviderModelsPayload;
|
|
8
|
+
export declare function normalizeProvidersSnapshotPayload<T extends GetProvidersSnapshotPayload | ProvidersSnapshotUpdatePayload>(payload: T): T;
|
|
9
|
+
export declare function normalizeProviderSnapshotUpdateMessage(msg: SessionOutboundMessage): SessionOutboundMessage;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=normalize-provider-models.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { normalizeAgentModelDefinition } from "@hyperdrive.bot/paseo-protocol/agent-types";
|
|
2
|
+
// COMPAT(model-normalize): daemon normalizes at source (provider-registry) — shim covers older daemons; drop when floor >= v0.1.104
|
|
3
|
+
function normalizeAgentModels(models) {
|
|
4
|
+
if (!models) {
|
|
5
|
+
return models;
|
|
6
|
+
}
|
|
7
|
+
let changed = false;
|
|
8
|
+
const normalized = models.map((model) => {
|
|
9
|
+
const next = normalizeAgentModelDefinition(model);
|
|
10
|
+
changed || (changed = next !== model);
|
|
11
|
+
return next;
|
|
12
|
+
});
|
|
13
|
+
return changed ? normalized : models;
|
|
14
|
+
}
|
|
15
|
+
function normalizeProviderSnapshotEntry(entry) {
|
|
16
|
+
const models = normalizeAgentModels(entry.models);
|
|
17
|
+
return models === entry.models ? entry : { ...entry, models };
|
|
18
|
+
}
|
|
19
|
+
function normalizeProviderSnapshotEntries(entries) {
|
|
20
|
+
let changed = false;
|
|
21
|
+
const normalized = entries.map((entry) => {
|
|
22
|
+
const next = normalizeProviderSnapshotEntry(entry);
|
|
23
|
+
changed || (changed = next !== entry);
|
|
24
|
+
return next;
|
|
25
|
+
});
|
|
26
|
+
return changed ? normalized : entries;
|
|
27
|
+
}
|
|
28
|
+
export function normalizeListProviderModelsPayload(payload) {
|
|
29
|
+
const models = normalizeAgentModels(payload.models);
|
|
30
|
+
return models === payload.models ? payload : { ...payload, models };
|
|
31
|
+
}
|
|
32
|
+
export function normalizeProvidersSnapshotPayload(payload) {
|
|
33
|
+
const entries = normalizeProviderSnapshotEntries(payload.entries);
|
|
34
|
+
return entries === payload.entries ? payload : { ...payload, entries };
|
|
35
|
+
}
|
|
36
|
+
export function normalizeProviderSnapshotUpdateMessage(msg) {
|
|
37
|
+
if (msg.type !== "providers_snapshot_update") {
|
|
38
|
+
return msg;
|
|
39
|
+
}
|
|
40
|
+
return { ...msg, payload: normalizeProvidersSnapshotPayload(msg.payload) };
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=normalize-provider-models.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DaemonTransport, DaemonTransportFactory, TransportLogger } from "./daemon-client-transport-types.js";
|
|
2
|
+
export declare function createRelayE2eeTransportFactory(args: {
|
|
3
|
+
baseFactory: DaemonTransportFactory;
|
|
4
|
+
daemonPublicKeyB64: string;
|
|
5
|
+
logger: TransportLogger;
|
|
6
|
+
}): DaemonTransportFactory;
|
|
7
|
+
export declare function createEncryptedTransport(base: DaemonTransport, daemonPublicKeyB64: string, logger: TransportLogger): DaemonTransport;
|
|
8
|
+
//# sourceMappingURL=daemon-client-relay-e2ee-transport.d.ts.map
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { createClientChannel, } from "@hyperdrive.bot/paseo-relay/e2ee";
|
|
2
|
+
import { extractRelayMessageData, normalizeTransportPayload, } from "./daemon-client-transport-utils.js";
|
|
3
|
+
export function createRelayE2eeTransportFactory(args) {
|
|
4
|
+
return ({ url, headers }) => {
|
|
5
|
+
const base = args.baseFactory({ url, headers });
|
|
6
|
+
return createEncryptedTransport(base, args.daemonPublicKeyB64, args.logger);
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function createEncryptedTransport(base, daemonPublicKeyB64, logger) {
|
|
10
|
+
let channel = null;
|
|
11
|
+
let opened = false;
|
|
12
|
+
let closed = false;
|
|
13
|
+
const openHandlers = new Set();
|
|
14
|
+
const closeHandlers = new Set();
|
|
15
|
+
const errorHandlers = new Set();
|
|
16
|
+
const messageHandlers = new Set();
|
|
17
|
+
const emitOpen = () => {
|
|
18
|
+
if (opened || closed) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
opened = true;
|
|
22
|
+
emitHandlers(openHandlers);
|
|
23
|
+
};
|
|
24
|
+
const emitClose = (event) => {
|
|
25
|
+
if (closed) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
closed = true;
|
|
29
|
+
emitHandlers(closeHandlers, event);
|
|
30
|
+
};
|
|
31
|
+
const emitError = (event) => {
|
|
32
|
+
if (closed) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
emitHandlers(errorHandlers, event);
|
|
36
|
+
};
|
|
37
|
+
const emitMessage = (data) => {
|
|
38
|
+
if (closed) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
emitHandlers(messageHandlers, data);
|
|
42
|
+
};
|
|
43
|
+
const relayTransport = {
|
|
44
|
+
send: (data) => {
|
|
45
|
+
if (typeof data === "string") {
|
|
46
|
+
base.send(data);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (ArrayBuffer.isView(data)) {
|
|
50
|
+
base.send(normalizeTransportPayload(data));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (data instanceof ArrayBuffer) {
|
|
54
|
+
base.send(data);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
base.send(String(data));
|
|
58
|
+
},
|
|
59
|
+
close: (code, reason) => base.close(code, reason),
|
|
60
|
+
onmessage: null,
|
|
61
|
+
onclose: null,
|
|
62
|
+
onerror: null,
|
|
63
|
+
};
|
|
64
|
+
const startHandshake = async () => {
|
|
65
|
+
try {
|
|
66
|
+
channel = await createClientChannel(relayTransport, daemonPublicKeyB64, {
|
|
67
|
+
onopen: emitOpen,
|
|
68
|
+
onmessage: (data) => emitMessage(data),
|
|
69
|
+
onclose: (code, reason) => emitClose({ code, reason }),
|
|
70
|
+
onerror: (error) => emitError(error),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
logger.warn({ err: normalizeTransportError(error) }, "relay_e2ee_handshake_failed");
|
|
75
|
+
emitError(error);
|
|
76
|
+
// Browser WebSocket.close only accepts 1000 or 3000-4999.
|
|
77
|
+
// Use an app-defined code so this path works in browser and Node runtimes.
|
|
78
|
+
base.close(4001, "E2EE handshake failed");
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
base.onOpen(() => {
|
|
82
|
+
void startHandshake();
|
|
83
|
+
});
|
|
84
|
+
base.onMessage((event) => {
|
|
85
|
+
relayTransport.onmessage?.(extractRelayMessageData(event));
|
|
86
|
+
});
|
|
87
|
+
base.onClose((event) => {
|
|
88
|
+
const record = event;
|
|
89
|
+
relayTransport.onclose?.(record?.code ?? 0, record?.reason ?? "");
|
|
90
|
+
emitClose(event);
|
|
91
|
+
});
|
|
92
|
+
base.onError((event) => {
|
|
93
|
+
relayTransport.onerror?.(event instanceof Error ? event : new Error(String(event)));
|
|
94
|
+
emitError(event);
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
send: (data) => {
|
|
98
|
+
if (!channel) {
|
|
99
|
+
throw new Error("Encrypted channel not ready");
|
|
100
|
+
}
|
|
101
|
+
void channel.send(normalizeTransportPayload(data)).catch((error) => {
|
|
102
|
+
emitError(error);
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
close: (code, reason) => {
|
|
106
|
+
if (channel) {
|
|
107
|
+
channel.close(code, reason);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
base.close(code, reason);
|
|
111
|
+
}
|
|
112
|
+
emitClose({ code, reason });
|
|
113
|
+
},
|
|
114
|
+
onMessage: (handler) => {
|
|
115
|
+
messageHandlers.add(handler);
|
|
116
|
+
return () => messageHandlers.delete(handler);
|
|
117
|
+
},
|
|
118
|
+
onOpen: (handler) => {
|
|
119
|
+
openHandlers.add(handler);
|
|
120
|
+
if (opened) {
|
|
121
|
+
invokeHandler(handler);
|
|
122
|
+
}
|
|
123
|
+
return () => openHandlers.delete(handler);
|
|
124
|
+
},
|
|
125
|
+
onClose: (handler) => {
|
|
126
|
+
closeHandlers.add(handler);
|
|
127
|
+
if (closed) {
|
|
128
|
+
invokeHandler(handler);
|
|
129
|
+
}
|
|
130
|
+
return () => closeHandlers.delete(handler);
|
|
131
|
+
},
|
|
132
|
+
onError: (handler) => {
|
|
133
|
+
errorHandlers.add(handler);
|
|
134
|
+
return () => errorHandlers.delete(handler);
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function emitHandlers(handlers, ...args) {
|
|
139
|
+
for (const handler of handlers) {
|
|
140
|
+
invokeHandler(handler, ...args);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function invokeHandler(handler, ...args) {
|
|
144
|
+
try {
|
|
145
|
+
handler(...args);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// no-op
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function normalizeTransportError(error) {
|
|
152
|
+
if (error instanceof Error) {
|
|
153
|
+
return {
|
|
154
|
+
name: error.name,
|
|
155
|
+
message: error.message,
|
|
156
|
+
...(typeof error.stack === "string" ? { stack: error.stack } : {}),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return { message: String(error) };
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=daemon-client-relay-e2ee-transport.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { SessionOutboundMessage } from "@hyperdrive.bot/paseo-protocol/messages";
|
|
2
|
+
interface RuntimeMetricsLogger {
|
|
3
|
+
info(obj: object, msg?: string): void;
|
|
4
|
+
}
|
|
5
|
+
interface RuntimeMetricsContext {
|
|
6
|
+
connectionPath: "direct" | "relay";
|
|
7
|
+
serverId: string | null;
|
|
8
|
+
getConnectionStatus: () => string;
|
|
9
|
+
}
|
|
10
|
+
interface RuntimeMetricsOptions {
|
|
11
|
+
windowMs?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare class DaemonClientRuntimeMetrics {
|
|
14
|
+
private readonly logger;
|
|
15
|
+
private readonly context;
|
|
16
|
+
private readonly startedAt;
|
|
17
|
+
private readonly windowMs;
|
|
18
|
+
private readonly buckets;
|
|
19
|
+
private readonly inboundMessageCounts;
|
|
20
|
+
private readonly inboundMessageBytes;
|
|
21
|
+
private readonly inboundMessageHandlerMs;
|
|
22
|
+
private readonly inboundAgentStreamCounts;
|
|
23
|
+
private readonly inboundAgentStreamByAgentCounts;
|
|
24
|
+
private readonly inboundBinaryFrameCounts;
|
|
25
|
+
constructor(logger: RuntimeMetricsLogger, context: RuntimeMetricsContext, options?: RuntimeMetricsOptions);
|
|
26
|
+
recordMessage(type: string, bytes: number, handlerMs: number): void;
|
|
27
|
+
recordAgentStream(payload: Extract<SessionOutboundMessage, {
|
|
28
|
+
type: "agent_stream";
|
|
29
|
+
}>["payload"]): void;
|
|
30
|
+
recordBinaryFrame(kind: string, bytes: number, handlerMs: number): void;
|
|
31
|
+
flush(options?: {
|
|
32
|
+
final?: boolean;
|
|
33
|
+
}): void;
|
|
34
|
+
private consumeCurrentBucket;
|
|
35
|
+
private pruneBuckets;
|
|
36
|
+
private aggregateBuckets;
|
|
37
|
+
}
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=daemon-client-runtime-metrics.d.ts.map
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
const DEFAULT_ROLLING_WINDOW_MS = 60000;
|
|
2
|
+
export class DaemonClientRuntimeMetrics {
|
|
3
|
+
constructor(logger, context, options) {
|
|
4
|
+
this.logger = logger;
|
|
5
|
+
this.context = context;
|
|
6
|
+
this.startedAt = Date.now();
|
|
7
|
+
this.buckets = [];
|
|
8
|
+
this.inboundMessageCounts = new Map();
|
|
9
|
+
this.inboundMessageBytes = new Map();
|
|
10
|
+
this.inboundMessageHandlerMs = new Map();
|
|
11
|
+
this.inboundAgentStreamCounts = new Map();
|
|
12
|
+
this.inboundAgentStreamByAgentCounts = new Map();
|
|
13
|
+
this.inboundBinaryFrameCounts = new Map();
|
|
14
|
+
this.windowMs =
|
|
15
|
+
typeof options?.windowMs === "number" && options.windowMs > 0
|
|
16
|
+
? options.windowMs
|
|
17
|
+
: DEFAULT_ROLLING_WINDOW_MS;
|
|
18
|
+
}
|
|
19
|
+
recordMessage(type, bytes, handlerMs) {
|
|
20
|
+
incrementCount(this.inboundMessageCounts, type, 1);
|
|
21
|
+
incrementCount(this.inboundMessageBytes, type, bytes);
|
|
22
|
+
incrementHandlerTiming(this.inboundMessageHandlerMs, type, handlerMs);
|
|
23
|
+
}
|
|
24
|
+
recordAgentStream(payload) {
|
|
25
|
+
const { agentId, event } = payload;
|
|
26
|
+
const eventType = event.type === "timeline" ? `timeline:${event.item.type}` : event.type;
|
|
27
|
+
incrementCount(this.inboundAgentStreamCounts, eventType, 1);
|
|
28
|
+
incrementCount(this.inboundAgentStreamByAgentCounts, agentId, 1);
|
|
29
|
+
}
|
|
30
|
+
recordBinaryFrame(kind, bytes, handlerMs) {
|
|
31
|
+
incrementCount(this.inboundBinaryFrameCounts, kind, 1);
|
|
32
|
+
incrementCount(this.inboundMessageBytes, `binary:${kind}`, bytes);
|
|
33
|
+
incrementHandlerTiming(this.inboundMessageHandlerMs, `binary:${kind}`, handlerMs);
|
|
34
|
+
}
|
|
35
|
+
flush(options) {
|
|
36
|
+
const now = Date.now();
|
|
37
|
+
const bucket = this.consumeCurrentBucket(now);
|
|
38
|
+
if (bucket) {
|
|
39
|
+
this.buckets.push(bucket);
|
|
40
|
+
}
|
|
41
|
+
this.pruneBuckets(now);
|
|
42
|
+
const aggregate = this.aggregateBuckets();
|
|
43
|
+
const hasActivity = aggregate.inboundMessageCounts.size > 0 || aggregate.inboundBinaryFrameCounts.size > 0;
|
|
44
|
+
if (!hasActivity && !options?.final) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this.logger.info({
|
|
48
|
+
windowMs: Math.min(this.windowMs, Math.max(0, now - this.startedAt)),
|
|
49
|
+
rollingWindowMs: this.windowMs,
|
|
50
|
+
bucketCount: this.buckets.length,
|
|
51
|
+
final: Boolean(options?.final),
|
|
52
|
+
connectionPath: this.context.connectionPath,
|
|
53
|
+
serverId: this.context.serverId,
|
|
54
|
+
connectionStatus: this.context.getConnectionStatus(),
|
|
55
|
+
inboundMessageTypesTop: getTopCounts(aggregate.inboundMessageCounts, 20),
|
|
56
|
+
inboundMessageBytesTop: getTopCounts(aggregate.inboundMessageBytes, 20),
|
|
57
|
+
inboundAgentStreamTypesTop: getTopCounts(aggregate.inboundAgentStreamCounts, 20),
|
|
58
|
+
inboundAgentStreamAgentsTop: getTopCounts(aggregate.inboundAgentStreamByAgentCounts, 20),
|
|
59
|
+
inboundBinaryFrameTypesTop: getTopCounts(aggregate.inboundBinaryFrameCounts, 12),
|
|
60
|
+
handlerTimingTop: getTopHandlerTimings(aggregate.inboundMessageHandlerMs, 20),
|
|
61
|
+
}, "ws_runtime_metrics_client");
|
|
62
|
+
}
|
|
63
|
+
consumeCurrentBucket(now) {
|
|
64
|
+
const hasActivity = this.inboundMessageCounts.size > 0 || this.inboundBinaryFrameCounts.size > 0;
|
|
65
|
+
if (!hasActivity) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const bucket = {
|
|
69
|
+
inboundMessageCounts: new Map(this.inboundMessageCounts),
|
|
70
|
+
inboundMessageBytes: new Map(this.inboundMessageBytes),
|
|
71
|
+
inboundMessageHandlerMs: cloneHandlerTimingMap(this.inboundMessageHandlerMs),
|
|
72
|
+
inboundAgentStreamCounts: new Map(this.inboundAgentStreamCounts),
|
|
73
|
+
inboundAgentStreamByAgentCounts: new Map(this.inboundAgentStreamByAgentCounts),
|
|
74
|
+
inboundBinaryFrameCounts: new Map(this.inboundBinaryFrameCounts),
|
|
75
|
+
endedAt: now,
|
|
76
|
+
};
|
|
77
|
+
this.inboundMessageCounts.clear();
|
|
78
|
+
this.inboundMessageBytes.clear();
|
|
79
|
+
this.inboundMessageHandlerMs.clear();
|
|
80
|
+
this.inboundAgentStreamCounts.clear();
|
|
81
|
+
this.inboundAgentStreamByAgentCounts.clear();
|
|
82
|
+
this.inboundBinaryFrameCounts.clear();
|
|
83
|
+
return bucket;
|
|
84
|
+
}
|
|
85
|
+
pruneBuckets(now) {
|
|
86
|
+
const cutoff = now - this.windowMs;
|
|
87
|
+
while (this.buckets.length > 0 && this.buckets[0].endedAt < cutoff) {
|
|
88
|
+
this.buckets.shift();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
aggregateBuckets() {
|
|
92
|
+
const aggregate = createEmptyBucket(Date.now());
|
|
93
|
+
for (const bucket of this.buckets) {
|
|
94
|
+
mergeCountMap(aggregate.inboundMessageCounts, bucket.inboundMessageCounts);
|
|
95
|
+
mergeCountMap(aggregate.inboundMessageBytes, bucket.inboundMessageBytes);
|
|
96
|
+
mergeHandlerTimingMap(aggregate.inboundMessageHandlerMs, bucket.inboundMessageHandlerMs);
|
|
97
|
+
mergeCountMap(aggregate.inboundAgentStreamCounts, bucket.inboundAgentStreamCounts);
|
|
98
|
+
mergeCountMap(aggregate.inboundAgentStreamByAgentCounts, bucket.inboundAgentStreamByAgentCounts);
|
|
99
|
+
mergeCountMap(aggregate.inboundBinaryFrameCounts, bucket.inboundBinaryFrameCounts);
|
|
100
|
+
}
|
|
101
|
+
return aggregate;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function createEmptyBucket(endedAt) {
|
|
105
|
+
return {
|
|
106
|
+
inboundMessageCounts: new Map(),
|
|
107
|
+
inboundMessageBytes: new Map(),
|
|
108
|
+
inboundMessageHandlerMs: new Map(),
|
|
109
|
+
inboundAgentStreamCounts: new Map(),
|
|
110
|
+
inboundAgentStreamByAgentCounts: new Map(),
|
|
111
|
+
inboundBinaryFrameCounts: new Map(),
|
|
112
|
+
endedAt,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function incrementCount(map, key, amount) {
|
|
116
|
+
map.set(key, (map.get(key) ?? 0) + amount);
|
|
117
|
+
}
|
|
118
|
+
function incrementHandlerTiming(map, key, handlerMs) {
|
|
119
|
+
const existing = map.get(key);
|
|
120
|
+
if (existing) {
|
|
121
|
+
existing.count += 1;
|
|
122
|
+
existing.totalMs += handlerMs;
|
|
123
|
+
existing.maxMs = Math.max(existing.maxMs, handlerMs);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
map.set(key, {
|
|
127
|
+
count: 1,
|
|
128
|
+
totalMs: handlerMs,
|
|
129
|
+
maxMs: handlerMs,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function cloneHandlerTimingMap(map) {
|
|
133
|
+
return new Map([...map.entries()].map(([key, value]) => [
|
|
134
|
+
key,
|
|
135
|
+
{ count: value.count, totalMs: value.totalMs, maxMs: value.maxMs },
|
|
136
|
+
]));
|
|
137
|
+
}
|
|
138
|
+
function mergeCountMap(target, source) {
|
|
139
|
+
for (const [key, value] of source) {
|
|
140
|
+
incrementCount(target, key, value);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function mergeHandlerTimingMap(target, source) {
|
|
144
|
+
for (const [key, value] of source) {
|
|
145
|
+
const existing = target.get(key);
|
|
146
|
+
if (existing) {
|
|
147
|
+
existing.count += value.count;
|
|
148
|
+
existing.totalMs += value.totalMs;
|
|
149
|
+
existing.maxMs = Math.max(existing.maxMs, value.maxMs);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
target.set(key, {
|
|
153
|
+
count: value.count,
|
|
154
|
+
totalMs: value.totalMs,
|
|
155
|
+
maxMs: value.maxMs,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function getTopCounts(map, limit) {
|
|
160
|
+
return [...map.entries()].sort((a, b) => b[1] - a[1]).slice(0, limit);
|
|
161
|
+
}
|
|
162
|
+
function getTopHandlerTimings(map, limit) {
|
|
163
|
+
const rows = [...map.entries()].map(([type, value]) => ({
|
|
164
|
+
type,
|
|
165
|
+
count: value.count,
|
|
166
|
+
totalMs: Math.round(value.totalMs),
|
|
167
|
+
avgMs: Math.round((value.totalMs / value.count) * 100) / 100,
|
|
168
|
+
maxMs: Math.round(value.maxMs * 100) / 100,
|
|
169
|
+
}));
|
|
170
|
+
rows.sort((a, b) => b.totalMs - a.totalMs);
|
|
171
|
+
return rows.slice(0, limit);
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=daemon-client-runtime-metrics.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface DaemonTransport {
|
|
2
|
+
send: (data: string | Uint8Array | ArrayBuffer) => void;
|
|
3
|
+
close: (code?: number, reason?: string) => void;
|
|
4
|
+
onMessage: (handler: (data: unknown) => void) => () => void;
|
|
5
|
+
onOpen: (handler: () => void) => () => void;
|
|
6
|
+
onClose: (handler: (event?: unknown) => void) => () => void;
|
|
7
|
+
onError: (handler: (event?: unknown) => void) => () => void;
|
|
8
|
+
}
|
|
9
|
+
export type DaemonTransportFactory = (options: {
|
|
10
|
+
url: string;
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
protocols?: string[];
|
|
13
|
+
}) => DaemonTransport;
|
|
14
|
+
export type WebSocketFactory = (url: string, options?: {
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
protocols?: string[];
|
|
17
|
+
}) => WebSocketLike;
|
|
18
|
+
export interface WebSocketLike {
|
|
19
|
+
readyState: number;
|
|
20
|
+
send: (data: string | Uint8Array | ArrayBuffer) => void;
|
|
21
|
+
close: (code?: number, reason?: string) => void;
|
|
22
|
+
binaryType?: string;
|
|
23
|
+
on?: (event: string, listener: (...args: unknown[]) => void) => void;
|
|
24
|
+
off?: (event: string, listener: (...args: unknown[]) => void) => void;
|
|
25
|
+
removeListener?: (event: string, listener: (...args: unknown[]) => void) => void;
|
|
26
|
+
addEventListener?: (event: string, listener: (event: unknown) => void) => void;
|
|
27
|
+
removeEventListener?: (event: string, listener: (event: unknown) => void) => void;
|
|
28
|
+
onopen?: ((event: unknown) => void) | null;
|
|
29
|
+
onclose?: ((event: unknown) => void) | null;
|
|
30
|
+
onerror?: ((event: unknown) => void) | null;
|
|
31
|
+
onmessage?: ((event: unknown) => void) | null;
|
|
32
|
+
}
|
|
33
|
+
export interface TransportLogger {
|
|
34
|
+
warn(obj: object, msg?: string): void;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=daemon-client-transport-types.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function copyArrayBufferViewToBuffer(data: ArrayBufferView): ArrayBuffer;
|
|
2
|
+
export declare function normalizeTransportPayload(data: string | Uint8Array | ArrayBuffer): string | ArrayBuffer;
|
|
3
|
+
export declare function extractRelayMessageData(event: unknown): string | ArrayBuffer;
|
|
4
|
+
export declare function describeTransportClose(event?: unknown): string;
|
|
5
|
+
export declare function describeTransportError(event?: unknown): string;
|
|
6
|
+
export declare function safeRandomId(): string;
|
|
7
|
+
export declare function decodeMessageData(data: unknown): string | null;
|
|
8
|
+
export declare function encodeUtf8String(value: string): Uint8Array;
|
|
9
|
+
//# sourceMappingURL=daemon-client-transport-utils.d.ts.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
export function copyArrayBufferViewToBuffer(data) {
|
|
2
|
+
const view = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
3
|
+
const out = new Uint8Array(view.byteLength);
|
|
4
|
+
out.set(view);
|
|
5
|
+
return out.buffer;
|
|
6
|
+
}
|
|
7
|
+
export function normalizeTransportPayload(data) {
|
|
8
|
+
if (typeof data === "string" || data instanceof ArrayBuffer) {
|
|
9
|
+
return data;
|
|
10
|
+
}
|
|
11
|
+
return copyArrayBufferViewToBuffer(data);
|
|
12
|
+
}
|
|
13
|
+
export function extractRelayMessageData(event) {
|
|
14
|
+
const raw = event && typeof event === "object" && "data" in event
|
|
15
|
+
? event.data
|
|
16
|
+
: event;
|
|
17
|
+
if (typeof raw === "string")
|
|
18
|
+
return raw;
|
|
19
|
+
if (raw instanceof ArrayBuffer)
|
|
20
|
+
return raw;
|
|
21
|
+
if (ArrayBuffer.isView(raw)) {
|
|
22
|
+
return copyArrayBufferViewToBuffer(raw);
|
|
23
|
+
}
|
|
24
|
+
return String(raw ?? "");
|
|
25
|
+
}
|
|
26
|
+
export function describeTransportClose(event) {
|
|
27
|
+
if (!event) {
|
|
28
|
+
return "Transport closed";
|
|
29
|
+
}
|
|
30
|
+
if (event instanceof Error) {
|
|
31
|
+
return event.message;
|
|
32
|
+
}
|
|
33
|
+
if (typeof event === "string") {
|
|
34
|
+
return event;
|
|
35
|
+
}
|
|
36
|
+
if (typeof event === "object") {
|
|
37
|
+
const record = event;
|
|
38
|
+
if (typeof record.reason === "string" && record.reason.trim().length > 0) {
|
|
39
|
+
return record.reason.trim();
|
|
40
|
+
}
|
|
41
|
+
if (typeof record.message === "string" && record.message.trim().length > 0) {
|
|
42
|
+
return record.message.trim();
|
|
43
|
+
}
|
|
44
|
+
if (typeof record.code === "number") {
|
|
45
|
+
return `Transport closed (code ${record.code})`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return "Transport closed";
|
|
49
|
+
}
|
|
50
|
+
export function describeTransportError(event) {
|
|
51
|
+
if (!event) {
|
|
52
|
+
return "Transport error";
|
|
53
|
+
}
|
|
54
|
+
if (event instanceof Error) {
|
|
55
|
+
return event.message;
|
|
56
|
+
}
|
|
57
|
+
if (typeof event === "string") {
|
|
58
|
+
return event;
|
|
59
|
+
}
|
|
60
|
+
if (typeof event === "object") {
|
|
61
|
+
const record = event;
|
|
62
|
+
if (typeof record.message === "string" && record.message.trim().length > 0) {
|
|
63
|
+
return record.message.trim();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return "Transport error";
|
|
67
|
+
}
|
|
68
|
+
export function safeRandomId() {
|
|
69
|
+
try {
|
|
70
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
71
|
+
return crypto.randomUUID();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// ignore
|
|
76
|
+
}
|
|
77
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
78
|
+
}
|
|
79
|
+
export function decodeMessageData(data) {
|
|
80
|
+
if (data === null || data === undefined) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
if (typeof data === "string") {
|
|
84
|
+
return data;
|
|
85
|
+
}
|
|
86
|
+
if (typeof ArrayBuffer !== "undefined" && data instanceof ArrayBuffer) {
|
|
87
|
+
if (typeof Buffer !== "undefined") {
|
|
88
|
+
return Buffer.from(data).toString("utf8");
|
|
89
|
+
}
|
|
90
|
+
if (typeof TextDecoder !== "undefined") {
|
|
91
|
+
return new TextDecoder().decode(data);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (ArrayBuffer.isView(data)) {
|
|
95
|
+
const view = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
96
|
+
if (typeof Buffer !== "undefined") {
|
|
97
|
+
return Buffer.from(view).toString("utf8");
|
|
98
|
+
}
|
|
99
|
+
if (typeof TextDecoder !== "undefined") {
|
|
100
|
+
return new TextDecoder().decode(view);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (typeof data.toString === "function") {
|
|
104
|
+
return data.toString();
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
export function encodeUtf8String(value) {
|
|
109
|
+
if (typeof TextEncoder !== "undefined") {
|
|
110
|
+
return new TextEncoder().encode(value);
|
|
111
|
+
}
|
|
112
|
+
if (typeof Buffer !== "undefined") {
|
|
113
|
+
return new Uint8Array(Buffer.from(value, "utf8"));
|
|
114
|
+
}
|
|
115
|
+
const out = new Uint8Array(value.length);
|
|
116
|
+
for (let i = 0; i < value.length; i++) {
|
|
117
|
+
out[i] = value.charCodeAt(i) & 0xff;
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=daemon-client-transport-utils.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { DaemonTransport, DaemonTransportFactory, TransportLogger, WebSocketFactory, WebSocketLike, } from "./daemon-client-transport-types.js";
|
|
2
|
+
export { decodeMessageData, describeTransportClose, describeTransportError, encodeUtf8String, extractRelayMessageData, normalizeTransportPayload, safeRandomId, } from "./daemon-client-transport-utils.js";
|
|
3
|
+
export { createEncryptedTransport, createRelayE2eeTransportFactory, } from "./daemon-client-relay-e2ee-transport.js";
|
|
4
|
+
export { bindWsHandler, createWebSocketTransportFactory, defaultWebSocketFactory, } from "./daemon-client-websocket-transport.js";
|
|
5
|
+
//# sourceMappingURL=daemon-client-transport.d.ts.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { decodeMessageData, describeTransportClose, describeTransportError, encodeUtf8String, extractRelayMessageData, normalizeTransportPayload, safeRandomId, } from "./daemon-client-transport-utils.js";
|
|
2
|
+
export { createEncryptedTransport, createRelayE2eeTransportFactory, } from "./daemon-client-relay-e2ee-transport.js";
|
|
3
|
+
export { bindWsHandler, createWebSocketTransportFactory, defaultWebSocketFactory, } from "./daemon-client-websocket-transport.js";
|
|
4
|
+
//# sourceMappingURL=daemon-client-transport.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DaemonTransportFactory, WebSocketFactory, WebSocketLike } from "./daemon-client-transport-types.js";
|
|
2
|
+
export declare function defaultWebSocketFactory(url: string, options?: {
|
|
3
|
+
headers?: Record<string, string>;
|
|
4
|
+
protocols?: string[];
|
|
5
|
+
}): WebSocketLike;
|
|
6
|
+
export declare function createWebSocketTransportFactory(factory: WebSocketFactory): DaemonTransportFactory;
|
|
7
|
+
export declare function bindWsHandler(ws: WebSocketLike, event: "open" | "close" | "error" | "message", handler: (...args: unknown[]) => void): () => void;
|
|
8
|
+
//# sourceMappingURL=daemon-client-websocket-transport.d.ts.map
|