Skip to content

Commit 21cd051

Browse files
committed
fix: validate mode at stats schema boundaries
MuxMetadata.mode is typed as string (for future custom modes), but the stats Zod schemas only accept "plan" | "exec" | undefined. The code was casting mode without validation, which TypeScript allowed but Zod rejected at runtime. This caused ZodError spam on every stream delta event, since each delta triggers getSnapshot() which parses the schema. Fix: validate mode before storing/emitting - treat unknown modes as undefined for stats purposes.
1 parent 32a9d27 commit 21cd051

File tree

3 files changed

+12
-4
lines changed

3 files changed

+12
-4
lines changed

src/common/types/message.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export interface MuxMetadata {
124124
// Readers should use helper: isCompacted = compacted !== undefined && compacted !== false
125125
compacted?: "user" | "idle" | boolean;
126126
toolPolicy?: ToolPolicy; // Tool policy active when this message was sent (user messages only)
127-
mode?: string; // The mode (plan/exec/etc) active when this message was sent (assistant messages only)
127+
mode?: string; // The mode active when this message was sent (assistant messages only) - plan/exec today, custom modes in future
128128
cmuxMetadata?: MuxFrontendMetadata; // Frontend-defined metadata, backend treats as black-box
129129
muxMetadata?: MuxFrontendMetadata; // Frontend-defined metadata, backend treats as black-box
130130
}

src/node/services/sessionTimingService.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,11 +482,15 @@ export class SessionTimingService {
482482

483483
const model = normalizeGatewayModel(data.model);
484484

485+
// Validate mode: stats schema only accepts "plan" | "exec" for now.
486+
// Custom modes will need schema updates when supported.
487+
const mode = data.mode === "plan" || data.mode === "exec" ? data.mode : undefined;
488+
485489
const state: ActiveStreamState = {
486490
workspaceId: data.workspaceId,
487491
messageId: data.messageId,
488492
model,
489-
mode: data.mode,
493+
mode,
490494
startTimeMs: data.startTime,
491495
firstTokenTimeMs: null,
492496
completedToolExecutionMs: 0,

src/node/services/streamManager.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -840,7 +840,9 @@ export class StreamManager extends EventEmitter {
840840
streamInfo.state = StreamState.STREAMING;
841841

842842
// Emit stream start event (include mode from initialMetadata if available)
843-
const streamStartMode = streamInfo.initialMetadata?.mode as "plan" | "exec" | undefined;
843+
// Validate mode - stats schema only accepts "plan" | "exec" for now
844+
const rawMode = streamInfo.initialMetadata?.mode;
845+
const streamStartMode = rawMode === "plan" || rawMode === "exec" ? rawMode : undefined;
844846
this.emit("stream-start", {
845847
type: "stream-start",
846848
workspaceId: workspaceId as string,
@@ -1743,7 +1745,9 @@ export class StreamManager extends EventEmitter {
17431745
await this.tokenTracker.setModel(streamInfo.model);
17441746

17451747
// Emit stream-start event (include mode from initialMetadata if available)
1746-
const replayMode = streamInfo.initialMetadata?.mode as "plan" | "exec" | undefined;
1748+
// Validate mode - stats schema only accepts "plan" | "exec" for now
1749+
const rawReplayMode = streamInfo.initialMetadata?.mode;
1750+
const replayMode = rawReplayMode === "plan" || rawReplayMode === "exec" ? rawReplayMode : undefined;
17471751
this.emit("stream-start", {
17481752
type: "stream-start",
17491753
workspaceId,

0 commit comments

Comments
 (0)