Skip to content

Commit addbac1

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 4e6ee27 commit addbac1

File tree

3 files changed

+13
-4
lines changed

3 files changed

+13
-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: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -865,7 +865,9 @@ export class StreamManager extends EventEmitter {
865865
streamInfo.state = StreamState.STREAMING;
866866

867867
// Emit stream start event (include mode from initialMetadata if available)
868-
const streamStartMode = streamInfo.initialMetadata?.mode as "plan" | "exec" | undefined;
868+
// Validate mode - stats schema only accepts "plan" | "exec" for now
869+
const rawMode = streamInfo.initialMetadata?.mode;
870+
const streamStartMode = rawMode === "plan" || rawMode === "exec" ? rawMode : undefined;
869871
this.emit("stream-start", {
870872
type: "stream-start",
871873
workspaceId: workspaceId as string,
@@ -1776,7 +1778,10 @@ export class StreamManager extends EventEmitter {
17761778
await this.tokenTracker.setModel(streamInfo.model);
17771779

17781780
// Emit stream-start event (include mode from initialMetadata if available)
1779-
const replayMode = streamInfo.initialMetadata?.mode as "plan" | "exec" | undefined;
1781+
// Validate mode - stats schema only accepts "plan" | "exec" for now
1782+
const rawReplayMode = streamInfo.initialMetadata?.mode;
1783+
const replayMode =
1784+
rawReplayMode === "plan" || rawReplayMode === "exec" ? rawReplayMode : undefined;
17801785
this.emit("stream-start", {
17811786
type: "stream-start",
17821787
workspaceId,

0 commit comments

Comments
 (0)