@otto-code/client 0.8.13 → 0.8.15
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.
|
@@ -32,6 +32,28 @@ export interface DaemonClientTrafficHotspot {
|
|
|
32
32
|
maxMs: number;
|
|
33
33
|
bytes: number;
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* One inbound session-message dispatch, retained only long enough to align it
|
|
37
|
+
* with a browser Long Animation Frame in the app's performance capture.
|
|
38
|
+
*
|
|
39
|
+
* `at` is an epoch timestamp so callers can compare it directly to the LoAF
|
|
40
|
+
* API. The phase timings are synchronous main-thread work and sum to
|
|
41
|
+
* approximately `totalMs`; small gaps are metric bookkeeping.
|
|
42
|
+
*/
|
|
43
|
+
export interface DaemonClientInboundDispatchTiming {
|
|
44
|
+
at: number;
|
|
45
|
+
type: string;
|
|
46
|
+
/** Set for agent-scoped messages, so a capture can tell one hot agent from a spread. */
|
|
47
|
+
agentId?: string;
|
|
48
|
+
bytes: number;
|
|
49
|
+
decodeAndValidateMs: number;
|
|
50
|
+
internalDispatchMs: number;
|
|
51
|
+
rawListenersMs: number;
|
|
52
|
+
typedHandlersMs: number;
|
|
53
|
+
totalMs: number;
|
|
54
|
+
}
|
|
55
|
+
/** Bounded independently from rolling log buckets: this is capture evidence, not telemetry. */
|
|
56
|
+
export declare const INBOUND_DISPATCH_TIMING_CAPACITY = 500;
|
|
35
57
|
export declare class DaemonClientRuntimeMetrics {
|
|
36
58
|
private readonly logger;
|
|
37
59
|
private readonly context;
|
|
@@ -49,11 +71,18 @@ export declare class DaemonClientRuntimeMetrics {
|
|
|
49
71
|
private totalHandlerMs;
|
|
50
72
|
private totalBinaryFrames;
|
|
51
73
|
private readonly cumulativeByType;
|
|
74
|
+
private readonly inboundDispatchTimings;
|
|
52
75
|
constructor(logger: RuntimeMetricsLogger, context: RuntimeMetricsContext, options?: RuntimeMetricsOptions);
|
|
53
76
|
recordMessage(type: string, bytes: number, handlerMs: number): void;
|
|
54
77
|
getTrafficTotals(): DaemonClientTrafficTotals;
|
|
55
78
|
/** Inbound message types ranked by the main-thread time they have cost. */
|
|
56
79
|
getTrafficHotspots(limit?: number): DaemonClientTrafficHotspot[];
|
|
80
|
+
recordInboundDispatch(timing: DaemonClientInboundDispatchTiming): void;
|
|
81
|
+
/**
|
|
82
|
+
* Recent inbound dispatches for a performance capture. Copies keep capture
|
|
83
|
+
* consumers from mutating a live client's bounded ring.
|
|
84
|
+
*/
|
|
85
|
+
getInboundDispatchTimings(sinceMs?: number): DaemonClientInboundDispatchTiming[];
|
|
57
86
|
private recordCumulative;
|
|
58
87
|
recordAgentStream(payload: Extract<SessionOutboundMessage, {
|
|
59
88
|
type: "agent_stream";
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const DEFAULT_ROLLING_WINDOW_MS = 60000;
|
|
2
|
+
/** Bounded independently from rolling log buckets: this is capture evidence, not telemetry. */
|
|
3
|
+
export const INBOUND_DISPATCH_TIMING_CAPACITY = 500;
|
|
2
4
|
export class DaemonClientRuntimeMetrics {
|
|
3
5
|
constructor(logger, context, options) {
|
|
4
6
|
this.logger = logger;
|
|
@@ -17,6 +19,7 @@ export class DaemonClientRuntimeMetrics {
|
|
|
17
19
|
this.totalHandlerMs = 0;
|
|
18
20
|
this.totalBinaryFrames = 0;
|
|
19
21
|
this.cumulativeByType = new Map();
|
|
22
|
+
this.inboundDispatchTimings = [];
|
|
20
23
|
this.windowMs =
|
|
21
24
|
typeof options?.windowMs === "number" && options.windowMs > 0
|
|
22
25
|
? options.windowMs
|
|
@@ -54,6 +57,21 @@ export class DaemonClientRuntimeMetrics {
|
|
|
54
57
|
rows.sort((left, right) => right.totalMs - left.totalMs);
|
|
55
58
|
return rows.slice(0, limit);
|
|
56
59
|
}
|
|
60
|
+
recordInboundDispatch(timing) {
|
|
61
|
+
this.inboundDispatchTimings.push(cloneInboundDispatchTiming(timing));
|
|
62
|
+
if (this.inboundDispatchTimings.length > INBOUND_DISPATCH_TIMING_CAPACITY) {
|
|
63
|
+
this.inboundDispatchTimings.splice(0, this.inboundDispatchTimings.length - INBOUND_DISPATCH_TIMING_CAPACITY);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Recent inbound dispatches for a performance capture. Copies keep capture
|
|
68
|
+
* consumers from mutating a live client's bounded ring.
|
|
69
|
+
*/
|
|
70
|
+
getInboundDispatchTimings(sinceMs) {
|
|
71
|
+
return this.inboundDispatchTimings
|
|
72
|
+
.filter((timing) => sinceMs === undefined || timing.at >= sinceMs)
|
|
73
|
+
.map(cloneInboundDispatchTiming);
|
|
74
|
+
}
|
|
57
75
|
// Shared by JSON messages and binary frames; the per-sink totals
|
|
58
76
|
// (`totalMessages` / `totalBinaryFrames`) are bumped by the callers so a
|
|
59
77
|
// binary frame is never counted as both.
|
|
@@ -192,6 +210,19 @@ function cloneHandlerTimingMap(map) {
|
|
|
192
210
|
{ count: value.count, totalMs: value.totalMs, maxMs: value.maxMs },
|
|
193
211
|
]));
|
|
194
212
|
}
|
|
213
|
+
function cloneInboundDispatchTiming(timing) {
|
|
214
|
+
return {
|
|
215
|
+
at: timing.at,
|
|
216
|
+
type: timing.type,
|
|
217
|
+
agentId: timing.agentId,
|
|
218
|
+
bytes: timing.bytes,
|
|
219
|
+
decodeAndValidateMs: timing.decodeAndValidateMs,
|
|
220
|
+
internalDispatchMs: timing.internalDispatchMs,
|
|
221
|
+
rawListenersMs: timing.rawListenersMs,
|
|
222
|
+
typedHandlersMs: timing.typedHandlersMs,
|
|
223
|
+
totalMs: timing.totalMs,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
195
226
|
function mergeCountMap(target, source) {
|
|
196
227
|
for (const [key, value] of source) {
|
|
197
228
|
incrementCount(target, key, value);
|
package/dist/daemon-client.d.ts
CHANGED
|
@@ -9,9 +9,10 @@ import type { OrchestrationGraph, PromptTemplate, Run } from "@otto-code/protoco
|
|
|
9
9
|
import type { BrainCatalogModel, BrainDiskUsage, BrainEvals, BrainHfSearchResult, BrainHostStatus, BrainInstalledModel, BrainInventoryModel, BrainJob, BrainLogsTailResponse, BrainLogsWatchResponse, BrainModelBudgetGetResponse, BrainModelDeleteResponse, BrainModelLoadResponse, BrainModelProfileGetResponse, BrainModelProfileSetResponse, BrainModelRenameResetResponse, BrainModelRenameResponse, BrainNetworkInfo, BrainRemoteConfig, BrainRepoQuant, BrainRuntime, ConnectorsListToolsResponse, ConnectorsOauthAuthorizeResponse, ConnectorsOauthDisconnectResponse, CommunicationsGetOverviewResponse, CommunicationsInboxGetHomeResponse, CommunicationsInboxNotificationsAcknowledgeResponse, CommunicationsInboxSearchResponse, CommunicationsInboxSetFavoriteResponse, CommunicationsInboxGetPresenceResponse, CommunicationsInboxGetMessagesResponse, CommunicationsInboxSetPresenceResponse, CommunicationsInboxSetEnabledResponse, CommunicationsInboxSendMessageResponse, CommunicationsRoomGetResponse, CommunicationsRoomThreadGetResponse, CommunicationsRoomMessageSendResponse, CommunicationsRoomReactionSetResponse, IntegrationsAuthorizationGetOverviewResponse, IntegrationsAuthorizationGetMethodsResponse, IntegrationsAuthorizationStartBrowserResponse, IntegrationsZoomStartAuthorizationResponse, CueMoment, MutableDaemonConfig, MutableDaemonConfigPatch, ProjectLink, SpeechSettingsOptions, SpeechTtsPreviewResult, SpeechTtsSpeakResult, SpeechTtsSpeakCancelResult, VisualizerVoiceCuesResult, AgentPersonalitiesGenerateProfileResult } from "@otto-code/protocol/messages";
|
|
10
10
|
import type { AgentConfigApply } from "@otto-code/protocol/messages";
|
|
11
11
|
import { type DaemonTransportFactory, type WebSocketFactory } from "./daemon-client-transport.js";
|
|
12
|
-
import { type DaemonClientTrafficHotspot, type DaemonClientTrafficTotals } from "./daemon-client-runtime-metrics.js";
|
|
12
|
+
import { type DaemonClientInboundDispatchTiming, type DaemonClientTrafficHotspot, type DaemonClientTrafficTotals } from "./daemon-client-runtime-metrics.js";
|
|
13
13
|
import { type TerminalStreamEvent } from "./terminal-stream-router.js";
|
|
14
14
|
import type { BrowserAutomationExecuteRequest, BrowserAutomationExecuteResponse } from "@otto-code/protocol/browser-automation/rpc-schemas";
|
|
15
|
+
export type { DaemonClientInboundDispatchTiming } from "./daemon-client-runtime-metrics.js";
|
|
15
16
|
export interface Logger {
|
|
16
17
|
debug(obj: object, msg?: string): void;
|
|
17
18
|
info(obj: object, msg?: string): void;
|
|
@@ -2157,6 +2158,12 @@ export declare class DaemonClient {
|
|
|
2157
2158
|
*/
|
|
2158
2159
|
getTrafficTotals(): DaemonClientTrafficTotals | null;
|
|
2159
2160
|
getTrafficHotspots(limit?: number): DaemonClientTrafficHotspot[];
|
|
2161
|
+
/**
|
|
2162
|
+
* Bounded, timestamped dispatch phases for matching an inbound daemon
|
|
2163
|
+
* message to a browser Long Animation Frame. This is diagnostic evidence,
|
|
2164
|
+
* not a protocol surface.
|
|
2165
|
+
*/
|
|
2166
|
+
getInboundDispatchTimings(sinceMs?: number): DaemonClientInboundDispatchTiming[];
|
|
2160
2167
|
private resolveTransportUrlForAttempt;
|
|
2161
2168
|
private sendHelloMessage;
|
|
2162
2169
|
private disposeTransport;
|
package/dist/daemon-client.js
CHANGED
|
@@ -186,6 +186,15 @@ function concatByteChunks(chunks, size) {
|
|
|
186
186
|
}
|
|
187
187
|
return bytes;
|
|
188
188
|
}
|
|
189
|
+
function extractDispatchAgentId(message) {
|
|
190
|
+
if (message.type === "agent_stream") {
|
|
191
|
+
return message.payload.agentId;
|
|
192
|
+
}
|
|
193
|
+
if (message.type === "agent_update") {
|
|
194
|
+
return message.payload.kind === "upsert" ? message.payload.agent.id : message.payload.agentId;
|
|
195
|
+
}
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
189
198
|
function getTransportFrameSize(frame) {
|
|
190
199
|
if (typeof frame === "string") {
|
|
191
200
|
return frame.length;
|
|
@@ -6055,6 +6064,14 @@ export class DaemonClient {
|
|
|
6055
6064
|
getTrafficHotspots(limit) {
|
|
6056
6065
|
return this.runtimeMetrics?.getTrafficHotspots(limit) ?? [];
|
|
6057
6066
|
}
|
|
6067
|
+
/**
|
|
6068
|
+
* Bounded, timestamped dispatch phases for matching an inbound daemon
|
|
6069
|
+
* message to a browser Long Animation Frame. This is diagnostic evidence,
|
|
6070
|
+
* not a protocol surface.
|
|
6071
|
+
*/
|
|
6072
|
+
getInboundDispatchTimings(sinceMs) {
|
|
6073
|
+
return this.runtimeMetrics?.getInboundDispatchTimings(sinceMs) ?? [];
|
|
6074
|
+
}
|
|
6058
6075
|
resolveTransportUrlForAttempt() {
|
|
6059
6076
|
return this.config.url;
|
|
6060
6077
|
}
|
|
@@ -6168,6 +6185,7 @@ export class DaemonClient {
|
|
|
6168
6185
|
}
|
|
6169
6186
|
handleJsonPayload(payload, rawBytesLength) {
|
|
6170
6187
|
const bytes = rawBytesLength ?? payload.length;
|
|
6188
|
+
const dispatchAt = Date.now();
|
|
6171
6189
|
const startMs = perfNow();
|
|
6172
6190
|
let parsedJson;
|
|
6173
6191
|
const parseTraceOpen = this.beginTraceSection("otto.ws.json.parse", {
|
|
@@ -6212,9 +6230,21 @@ export class DaemonClient {
|
|
|
6212
6230
|
envelopeType: "session",
|
|
6213
6231
|
messageType: parsed.data.message.type,
|
|
6214
6232
|
});
|
|
6215
|
-
this.handleSessionMessage(parsed.data.message);
|
|
6233
|
+
const phases = this.handleSessionMessage(parsed.data.message);
|
|
6216
6234
|
const msgType = parsed.data.message.type;
|
|
6217
|
-
|
|
6235
|
+
const totalMs = perfNow() - startMs;
|
|
6236
|
+
this.runtimeMetrics?.recordMessage(msgType, bytes, totalMs);
|
|
6237
|
+
this.runtimeMetrics?.recordInboundDispatch({
|
|
6238
|
+
at: dispatchAt,
|
|
6239
|
+
type: msgType,
|
|
6240
|
+
agentId: extractDispatchAgentId(parsed.data.message),
|
|
6241
|
+
bytes,
|
|
6242
|
+
decodeAndValidateMs: phases.startedAtMs - startMs,
|
|
6243
|
+
internalDispatchMs: phases.internalDispatchMs,
|
|
6244
|
+
rawListenersMs: phases.rawListenersMs,
|
|
6245
|
+
typedHandlersMs: phases.typedHandlersMs,
|
|
6246
|
+
totalMs,
|
|
6247
|
+
});
|
|
6218
6248
|
if (parsed.data.message.type === "agent_stream") {
|
|
6219
6249
|
this.runtimeMetrics?.recordAgentStream(parsed.data.message.payload);
|
|
6220
6250
|
}
|
|
@@ -6426,6 +6456,7 @@ export class DaemonClient {
|
|
|
6426
6456
|
});
|
|
6427
6457
|
}
|
|
6428
6458
|
handleSessionMessage(msg) {
|
|
6459
|
+
const startedAtMs = perfNow();
|
|
6429
6460
|
const consumerMessage = normalizeProviderSnapshotUpdateMessage(msg);
|
|
6430
6461
|
if (consumerMessage.type === "status") {
|
|
6431
6462
|
const serverInfo = parseServerInfoStatusPayload(consumerMessage.payload);
|
|
@@ -6453,6 +6484,7 @@ export class DaemonClient {
|
|
|
6453
6484
|
if (consumerMessage.type === "project.scaffold.progress") {
|
|
6454
6485
|
this.scaffoldProgressListeners.get(consumerMessage.payload.requestId)?.(consumerMessage.payload);
|
|
6455
6486
|
}
|
|
6487
|
+
const rawListenersStartedAtMs = perfNow();
|
|
6456
6488
|
if (this.rawMessageListeners.size > 0) {
|
|
6457
6489
|
for (const handler of this.rawMessageListeners) {
|
|
6458
6490
|
try {
|
|
@@ -6463,6 +6495,7 @@ export class DaemonClient {
|
|
|
6463
6495
|
}
|
|
6464
6496
|
}
|
|
6465
6497
|
}
|
|
6498
|
+
const typedHandlersStartedAtMs = perfNow();
|
|
6466
6499
|
const handlers = this.messageHandlers.get(consumerMessage.type);
|
|
6467
6500
|
if (handlers) {
|
|
6468
6501
|
for (const handler of handlers) {
|
|
@@ -6481,6 +6514,13 @@ export class DaemonClient {
|
|
|
6481
6514
|
}
|
|
6482
6515
|
}
|
|
6483
6516
|
this.resolveWaiters(consumerMessage);
|
|
6517
|
+
const finishedAtMs = perfNow();
|
|
6518
|
+
return {
|
|
6519
|
+
startedAtMs,
|
|
6520
|
+
internalDispatchMs: rawListenersStartedAtMs - startedAtMs,
|
|
6521
|
+
rawListenersMs: typedHandlersStartedAtMs - rawListenersStartedAtMs,
|
|
6522
|
+
typedHandlersMs: finishedAtMs - typedHandlersStartedAtMs,
|
|
6523
|
+
};
|
|
6484
6524
|
}
|
|
6485
6525
|
resolveWaiters(msg) {
|
|
6486
6526
|
for (const waiter of Array.from(this.waiters)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@otto-code/client",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.15",
|
|
4
4
|
"description": "Otto client SDK package",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"files": [
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"test": "vitest run"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@otto-code/protocol": "0.8.
|
|
44
|
-
"@otto-code/relay": "0.8.
|
|
43
|
+
"@otto-code/protocol": "0.8.15",
|
|
44
|
+
"@otto-code/relay": "0.8.15",
|
|
45
45
|
"zod": "^4.4.3"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|