Skip to content

Commit faa03d3

Browse files
authored
Merge pull request #1 from GhostDragon124/fix/eliminate-as-any
fix: 消除 8 个 as any — MCP 通知、结构化输出、流事件的类型安全修复
2 parents a972ed7 + 16712e0 commit faa03d3

7 files changed

Lines changed: 79 additions & 41 deletions

File tree

src/entrypoints/cli.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ async function main(): Promise<void> {
146146
shutdown1PEventLogging,
147147
logForDebugging,
148148
registerPermissionHandler(server, handler) {
149-
server.setNotificationHandler(ChannelPermissionRequestNotificationSchema() as any, async notification =>
149+
server.setNotificationHandler(ChannelPermissionRequestNotificationSchema(), async notification =>
150150
handler(notification.params),
151151
);
152152
},

src/hooks/useIdeLogging.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { logEvent } from 'src/services/analytics/index.js'
33
import { z } from 'zod/v4'
44
import type { MCPServerConnection } from '../services/mcp/types.js'
55
import { getConnectedIdeClient } from '../utils/ide.js'
6+
import type { AnyObjectSchema } from '@modelcontextprotocol/sdk/server/zod-compat.js'
67
import { lazySchema } from '../utils/lazySchema.js'
78

8-
const LogEventSchema = lazySchema(() =>
9+
const LogEventSchema: () => AnyObjectSchema = lazySchema(() =>
910
z.object({
1011
method: z.literal('log_event'),
1112
params: z.object({
@@ -27,7 +28,7 @@ export function useIdeLogging(mcpClients: MCPServerConnection[]): void {
2728
if (ideClient) {
2829
// Register the log event handler
2930
ideClient.client.setNotificationHandler(
30-
LogEventSchema() as any,
31+
LogEventSchema(),
3132
notification => {
3233
const { eventName, eventData } = notification.params
3334
logEvent(

src/hooks/useIdeSelection.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
MCPServerConnection,
77
} from '../services/mcp/types.js'
88
import { getConnectedIdeClient } from '../utils/ide.js'
9+
import type { AnyObjectSchema } from '@modelcontextprotocol/sdk/server/zod-compat.js'
910
import { lazySchema } from '../utils/lazySchema.js'
1011
export type SelectionPoint = {
1112
line: number
@@ -29,7 +30,7 @@ export type IDESelection = {
2930
}
3031

3132
// Define the selection changed notification schema
32-
const SelectionChangedSchema = lazySchema(() =>
33+
const SelectionChangedSchema: () => AnyObjectSchema = lazySchema(() =>
3334
z.object({
3435
method: z.literal('selection_changed'),
3536
params: z.object({
@@ -110,7 +111,7 @@ export function useIdeSelection(
110111

111112
// Register notification handler for selection_changed events
112113
ideClient.client.setNotificationHandler(
113-
SelectionChangedSchema() as any,
114+
SelectionChangedSchema(),
114115
notification => {
115116
if (currentIDERef.current !== ideClient) {
116117
return

src/hooks/usePromptsFromClaudeInChrome.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ import { callIdeRpc } from '../services/mcp/client.js';
66
import type { ConnectedMCPServer, MCPServerConnection } from '../services/mcp/types.js';
77
import type { PermissionMode } from '../types/permissions.js';
88
import { CLAUDE_IN_CHROME_MCP_SERVER_NAME, isTrackedClaudeInChromeTabId } from '../utils/claudeInChrome/common.js';
9+
import type { AnyObjectSchema } from '@modelcontextprotocol/sdk/server/zod-compat.js';
910
import { lazySchema } from '../utils/lazySchema.js';
1011
import { enqueuePendingNotification } from '../utils/messageQueueManager.js';
1112

1213
// Schema for the prompt notification from Chrome extension (JSON-RPC 2.0 format)
13-
const ClaudeInChromePromptNotificationSchema = lazySchema(() =>
14+
const ClaudeInChromePromptNotificationSchema: () => AnyObjectSchema = lazySchema(() =>
1415
z.object({
1516
method: z.literal('notifications/message'),
1617
params: z.object({
@@ -48,7 +49,7 @@ export function usePromptsFromClaudeInChrome(
4849
}
4950

5051
if (mcpClient) {
51-
mcpClient.client.setNotificationHandler(ClaudeInChromePromptNotificationSchema() as any, notification => {
52+
mcpClient.client.setNotificationHandler(ClaudeInChromePromptNotificationSchema(), notification => {
5253
if (mcpClientRef.current !== mcpClient) {
5354
return;
5455
}

src/services/mcp/channelNotification.ts

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818

1919
import type { ServerCapabilities } from '@modelcontextprotocol/sdk/types.js'
20+
import type { AnyObjectSchema } from '@modelcontextprotocol/sdk/server/zod-compat.js'
2021
import { z } from 'zod/v4'
2122
import { type ChannelEntry, getAllowedChannels } from '../../bootstrap/state.js'
2223
import { CHANNEL_TAG } from '../../constants/xml.js'
@@ -96,23 +97,24 @@ export type ChannelPermissionRequestParams = {
9697
}
9798
}
9899

99-
export const ChannelPermissionRequestNotificationSchema = lazySchema(() =>
100-
z.object({
101-
method: z.literal(CHANNEL_PERMISSION_REQUEST_METHOD),
102-
params: z.object({
103-
request_id: z.string(),
104-
tool_name: z.string(),
105-
description: z.string(),
106-
input_preview: z.string(),
107-
channel_context: z
108-
.object({
109-
source_server: z.string().optional(),
110-
chat_id: z.string().optional(),
111-
})
112-
.optional(),
100+
export const ChannelPermissionRequestNotificationSchema: () => AnyObjectSchema =
101+
lazySchema(() =>
102+
z.object({
103+
method: z.literal(CHANNEL_PERMISSION_REQUEST_METHOD),
104+
params: z.object({
105+
request_id: z.string(),
106+
tool_name: z.string(),
107+
description: z.string(),
108+
input_preview: z.string(),
109+
channel_context: z
110+
.object({
111+
source_server: z.string().optional(),
112+
chat_id: z.string().optional(),
113+
})
114+
.optional(),
115+
}),
113116
}),
114-
}),
115-
)
117+
)
116118

117119
/**
118120
* Meta keys become XML attribute NAMES — a crafted key like

src/utils/forkedAgent.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,14 @@ import {
2020
} from '../services/analytics/index.js'
2121
import { accumulateUsage, updateUsage } from '../services/api/claude.js'
2222
import { EMPTY_USAGE, type NonNullableUsage } from '@ant/model-provider'
23+
import type {
24+
BetaRawMessageDeltaEvent,
25+
BetaRawMessageStreamEvent,
26+
} from '@anthropic-ai/sdk/resources/beta/messages/messages.js'
2327
import type { ToolUseContext } from '../Tool.js'
2428
import type { AgentDefinition } from '@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js'
2529
import type { AgentId } from '../types/ids.js'
26-
import type { Message } from '../types/message.js'
30+
import type { Message, StreamEvent } from '../types/message.js'
2731
import { createChildAbortController } from './abortController.js'
2832
import { logForDebugging } from './debug.js'
2933
import { cloneFileStateCache } from './fileStateCache.js'
@@ -492,6 +496,24 @@ export function createSubagentContext(
492496
* })
493497
* ```
494498
*/
499+
500+
type StreamEventMessage = StreamEvent & {
501+
type: 'stream_event'
502+
event: BetaRawMessageStreamEvent
503+
}
504+
505+
function isMessageDeltaStreamEvent(
506+
message: Message | StreamEvent,
507+
): message is StreamEventMessage & { event: BetaRawMessageDeltaEvent } {
508+
return (
509+
message.type === 'stream_event' &&
510+
typeof (message as StreamEventMessage).event === 'object' &&
511+
(message as StreamEventMessage).event !== null &&
512+
'type' in (message as StreamEventMessage).event &&
513+
(message as StreamEventMessage).event.type === 'message_delta'
514+
)
515+
}
516+
495517
export async function runForkedAgent({
496518
promptMessages,
497519
cacheSafeParams,
@@ -562,15 +584,8 @@ export async function runForkedAgent({
562584
})) {
563585
// Extract real usage from message_delta stream events (final usage per API call)
564586
if (message.type === 'stream_event') {
565-
if (
566-
'event' in message &&
567-
(message as any).event?.type === 'message_delta' &&
568-
(message as any).event.usage
569-
) {
570-
const turnUsage = updateUsage(
571-
{ ...EMPTY_USAGE },
572-
(message as any).event.usage,
573-
)
587+
if (isMessageDeltaStreamEvent(message)) {
588+
const turnUsage = updateUsage({ ...EMPTY_USAGE }, message.event.usage)
574589
totalUsage = accumulateUsage(totalUsage, turnUsage)
575590
}
576591
continue

src/utils/hooks/execAgentHook.ts

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ import { type Tool, toolMatchesName } from '../../Tool.js'
88
import { SYNTHETIC_OUTPUT_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/SyntheticOutputTool/SyntheticOutputTool.js'
99
import { ALL_AGENT_DISALLOWED_TOOLS } from '../../tools.js'
1010
import { asAgentId } from '../../types/ids.js'
11-
import type { Message } from '../../types/message.js'
11+
import type {
12+
AttachmentMessage,
13+
Message,
14+
RequestStartEvent,
15+
StreamEvent,
16+
} from '../../types/message.js'
1217
import { createAbortController } from '../abortController.js'
1318
import { createAttachmentMessage } from '../attachments.js'
1419
import { createCombinedAbortSignal } from '../combinedAbortSignal.js'
@@ -30,6 +35,24 @@ import {
3035
} from './hookHelpers.js'
3136
import { clearSessionHooks } from './sessionHooks.js'
3237

38+
type QueryMessage = Message | StreamEvent | RequestStartEvent
39+
40+
type StructuredOutputAttachment = {
41+
type: 'structured_output'
42+
data: unknown
43+
[key: string]: unknown
44+
}
45+
46+
type StructuredOutputAttachmentMessage =
47+
AttachmentMessage<StructuredOutputAttachment>
48+
49+
function isStructuredOutputAttachmentMessage(
50+
message: QueryMessage,
51+
): message is StructuredOutputAttachmentMessage {
52+
if (message.type !== 'attachment') return false
53+
return (message as Message).attachment?.type === 'structured_output'
54+
}
55+
3356
/**
3457
* Execute an agent-based hook using a multi-turn LLM query
3558
*/
@@ -209,13 +232,8 @@ When done, return your result using the ${SYNTHETIC_OUTPUT_TOOL_NAME} tool with:
209232
}
210233

211234
// Check for structured output in attachments
212-
if (
213-
message.type === 'attachment' &&
214-
(message as any).attachment.type === 'structured_output'
215-
) {
216-
const parsed = hookResponseSchema().safeParse(
217-
(message as any).attachment.data,
218-
)
235+
if (isStructuredOutputAttachmentMessage(message)) {
236+
const parsed = hookResponseSchema().safeParse(message.attachment.data)
219237
if (parsed.success) {
220238
structuredOutputResult = parsed.data
221239
logForDebugging(

0 commit comments

Comments
 (0)