Skip to content

Commit 5961058

Browse files
committed
refactor: improve tool part schema type safety and remove dead code
1. Refactor MuxToolPartSchema using extend pattern for DRY code: - Base schema shares common fields (type, toolCallId, toolName, input, timestamp) - Pending variant: state="input-available", no output field - Available variant: state="output-available", output required 2. Remove dead ReasoningStartEventSchema: - Schema was defined but never emitted by backend - Not in WorkspaceChatMessageSchema union - No type guard existed for it 3. Update MuxToolPart type to use Zod inference: - Type now properly discriminates based on state - Accessing output requires narrowing to output-available state
1 parent 75877fc commit 5961058

File tree

6 files changed

+18
-44
lines changed

6 files changed

+18
-44
lines changed

src/browser/utils/messages/StreamingMessageAggregator.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -419,12 +419,11 @@ export class StreamingMessageAggregator {
419419
if (data.parts) {
420420
// Sync up the tool results from the backend's parts array
421421
for (const backendPart of data.parts) {
422-
if (backendPart.type === "dynamic-tool") {
422+
if (backendPart.type === "dynamic-tool" && backendPart.state === "output-available") {
423423
// Find and update existing tool part
424424
const toolPart = message.parts.find(
425425
(part): part is DynamicToolPart =>
426-
part.type === "dynamic-tool" &&
427-
(part as DynamicToolPart).toolCallId === backendPart.toolCallId
426+
part.type === "dynamic-tool" && part.toolCallId === backendPart.toolCallId
428427
);
429428
if (toolPart) {
430429
// Update with result from backend
@@ -510,7 +509,7 @@ export class StreamingMessageAggregator {
510509
// Check if this tool call already exists to prevent duplicates
511510
const existingToolPart = message.parts.find(
512511
(part): part is DynamicToolPart =>
513-
part.type === "dynamic-tool" && (part as DynamicToolPart).toolCallId === data.toolCallId
512+
part.type === "dynamic-tool" && part.toolCallId === data.toolCallId
514513
);
515514

516515
if (existingToolPart) {

src/common/orpc/schemas.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ export {
7171
QueuedMessageChangedEventSchema,
7272
ReasoningDeltaEventSchema,
7373
ReasoningEndEventSchema,
74-
ReasoningStartEventSchema,
7574
RestoreToInputEventSchema,
7675
SendMessageOptionsSchema,
7776
StreamAbortEventSchema,

src/common/orpc/schemas/message.ts

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,41 +19,32 @@ export const MuxReasoningPartSchema = z.object({
1919
timestamp: z.number().optional(),
2020
});
2121

22-
// Discriminated tool part schemas for proper type inference
23-
export const DynamicToolPartAvailableSchema = z.object({
22+
// Base schema for tool parts - shared fields
23+
const MuxToolPartBase = z.object({
2424
type: z.literal("dynamic-tool"),
2525
toolCallId: z.string(),
2626
toolName: z.string(),
27-
state: z.literal("output-available"),
2827
input: z.unknown(),
29-
output: z.unknown(),
3028
timestamp: z.number().optional(),
3129
});
3230

33-
export const DynamicToolPartPendingSchema = z.object({
34-
type: z.literal("dynamic-tool"),
35-
toolCallId: z.string(),
36-
toolName: z.string(),
31+
// Discriminated tool part schemas - output required only when state is "output-available"
32+
export const DynamicToolPartPendingSchema = MuxToolPartBase.extend({
3733
state: z.literal("input-available"),
38-
input: z.unknown(),
39-
timestamp: z.number().optional(),
34+
});
35+
36+
export const DynamicToolPartAvailableSchema = MuxToolPartBase.extend({
37+
state: z.literal("output-available"),
38+
output: z.unknown(),
4039
});
4140

4241
export const DynamicToolPartSchema = z.discriminatedUnion("state", [
4342
DynamicToolPartAvailableSchema,
4443
DynamicToolPartPendingSchema,
4544
]);
4645

47-
// Alias for backward compatibility - used in message schemas
48-
export const MuxToolPartSchema = z.object({
49-
type: z.literal("dynamic-tool"),
50-
toolCallId: z.string(),
51-
toolName: z.string(),
52-
state: z.enum(["input-available", "output-available"]),
53-
input: z.unknown(),
54-
output: z.unknown().optional(),
55-
timestamp: z.number().optional(),
56-
});
46+
// Alias for message schemas
47+
export const MuxToolPartSchema = DynamicToolPartSchema;
5748

5849
export const MuxImagePartSchema = z.object({
5950
type: z.literal("file"),

src/common/orpc/schemas/stream.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,6 @@ export const ToolCallEndEventSchema = z.object({
154154
timestamp: z.number().meta({ description: "When tool call completed (Date.now())" }),
155155
});
156156

157-
export const ReasoningStartEventSchema = z.object({
158-
type: z.literal("reasoning-start"),
159-
workspaceId: z.string(),
160-
messageId: z.string(),
161-
});
162-
163157
export const ReasoningDeltaEventSchema = z.object({
164158
type: z.literal("reasoning-delta"),
165159
workspaceId: z.string(),

src/common/types/message.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import type { LanguageModelV2Usage } from "@ai-sdk/provider";
33
import type { StreamErrorType } from "./errors";
44
import type { ToolPolicy } from "@/common/utils/tools/toolPolicy";
55
import type { ChatUsageDisplay } from "@/common/utils/tokens/usageAggregator";
6-
import type { ImagePart } from "@/common/orpc/schemas";
6+
import type { ImagePart, MuxToolPartSchema } from "@/common/orpc/schemas";
7+
import type { z } from "zod";
78

89
// Message to continue with after compaction
910
export interface ContinueMessage {
@@ -54,15 +55,8 @@ export interface MuxMetadata {
5455

5556
// Extended tool part type that supports interrupted tool calls (input-available state)
5657
// Standard AI SDK ToolUIPart only supports output-available (completed tools)
57-
export interface MuxToolPart {
58-
type: "dynamic-tool";
59-
toolCallId: string;
60-
toolName: string;
61-
state: "input-available" | "output-available";
62-
input: unknown;
63-
output?: unknown;
64-
timestamp?: number; // When the tool call was emitted
65-
}
58+
// Uses discriminated union: output is required when state is "output-available", absent when "input-available"
59+
export type MuxToolPart = z.infer<typeof MuxToolPartSchema>;
6660

6761
// Text part type
6862
export interface MuxTextPart {

src/common/types/stream.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import type {
88
ErrorEventSchema,
99
ReasoningDeltaEventSchema,
1010
ReasoningEndEventSchema,
11-
ReasoningStartEventSchema,
1211
StreamAbortEventSchema,
1312
StreamDeltaEventSchema,
1413
StreamEndEventSchema,
@@ -36,7 +35,6 @@ export type ToolCallStartEvent = z.infer<typeof ToolCallStartEventSchema>;
3635
export type ToolCallDeltaEvent = z.infer<typeof ToolCallDeltaEventSchema>;
3736
export type ToolCallEndEvent = z.infer<typeof ToolCallEndEventSchema>;
3837

39-
export type ReasoningStartEvent = z.infer<typeof ReasoningStartEventSchema>;
4038
export type ReasoningDeltaEvent = z.infer<typeof ReasoningDeltaEventSchema>;
4139
export type ReasoningEndEvent = z.infer<typeof ReasoningEndEventSchema>;
4240

@@ -55,7 +53,6 @@ export type AIServiceEvent =
5553
| ToolCallStartEvent
5654
| ToolCallDeltaEvent
5755
| ToolCallEndEvent
58-
| ReasoningStartEvent
5956
| ReasoningDeltaEvent
6057
| ReasoningEndEvent
6158
| UsageDeltaEvent;

0 commit comments

Comments
 (0)