fix(tsc): Phase 3a — fix 34 type errors in AgentTool/UI.tsx (175→141)

- Add local type assertions (AgentProgressMessage, AgentToolProgressData)
- No as any used — all specific type narrowing
- AgentTool tests: 40 pass, 0 fail
This commit is contained in:
James Feng 2026-06-07 11:03:08 +08:00
parent a648196c3c
commit 2c0a35d04b

View File

@ -22,7 +22,7 @@ import { ToolUseLoader } from 'src/components/ToolUseLoader.js'
import { Box, Text } from '@anthropic/ink' import { Box, Text } from '@anthropic/ink'
import { getDumpPromptsPath } from 'src/services/api/dumpPrompts.js' import { getDumpPromptsPath } from 'src/services/api/dumpPrompts.js'
import { findToolByName, type Tools } from 'src/Tool.js' import { findToolByName, type Tools } from 'src/Tool.js'
import type { Message, ProgressMessage } from 'src/types/message.js' import type { AssistantMessage, Message, ProgressMessage, UserMessage } from 'src/types/message.js'
import type { AgentToolProgress } from 'src/types/tools.js' import type { AgentToolProgress } from 'src/types/tools.js'
import { count } from 'src/utils/array.js' import { count } from 'src/utils/array.js'
import { import {
@ -68,6 +68,29 @@ function hasProgressMessage(data: Progress): data is AgentToolProgress {
return msg != null && typeof msg === 'object' && 'type' in msg return msg != null && typeof msg === 'object' && 'type' in msg
} }
/**
* Local type describing the actual runtime shape of a progress message's `message`
* field. AgentToolProgress is `Record<string, unknown>` so we need concrete types
* for property access without resorting to `as any`.
*/
type AgentProgressMessage = {
type: 'assistant' | 'user'
message: {
content: BetaContentBlock[]
usage?: BetaUsage
}
toolUseResult?: ToolResultBlockParam
}
/**
* Local type for AgentToolProgress data when it contains a progress message.
*/
type AgentToolProgressData = {
message: AgentProgressMessage
prompt?: string
agentId?: string
}
/** /**
* Check if a progress message is a search/read/REPL operation (tool use or result). * Check if a progress message is a search/read/REPL operation (tool use or result).
* Returns { isSearch, isRead, isREPL } if it's a collapsible operation, null otherwise. * Returns { isSearch, isRead, isREPL } if it's a collapsible operation, null otherwise.
@ -83,7 +106,7 @@ function getSearchOrReadInfo(
if (!hasProgressMessage(progressMessage.data)) { if (!hasProgressMessage(progressMessage.data)) {
return null return null
} }
const message = progressMessage.data.message const message = progressMessage.data.message as AgentProgressMessage
// Check tool_use (assistant message) // Check tool_use (assistant message)
if (message.type === 'assistant') { if (message.type === 'assistant') {
@ -132,7 +155,7 @@ function processProgressMessages(
return messages return messages
.filter( .filter(
(m): m is ProgressMessage<AgentToolProgress> => (m): m is ProgressMessage<AgentToolProgress> =>
hasProgressMessage(m.data) && m.data.message.type !== 'user', hasProgressMessage(m.data) && (m.data as AgentToolProgressData).message.type !== 'user',
) )
.map(m => ({ type: 'original', message: m })) .map(m => ({ type: 'original', message: m }))
} }
@ -172,8 +195,8 @@ function processProgressMessages(
const toolUseByID = new Map<string, ToolUseBlockParam>() const toolUseByID = new Map<string, ToolUseBlockParam>()
for (const msg of agentMessages) { for (const msg of agentMessages) {
// Track tool_use blocks as we see them // Track tool_use blocks as we see them
if (msg.data.message.type === 'assistant') { if ((msg.data as AgentToolProgressData).message.type === 'assistant') {
for (const c of msg.data.message.message.content) { for (const c of (msg.data as AgentToolProgressData).message.message.content) {
if (c.type === 'tool_use') { if (c.type === 'tool_use') {
toolUseByID.set(c.id, c as ToolUseBlockParam) toolUseByID.set(c.id, c as ToolUseBlockParam)
} }
@ -192,7 +215,7 @@ function processProgressMessages(
} }
} }
// Only count tool_result messages (not tool_use) to avoid double counting // Only count tool_result messages (not tool_use) to avoid double counting
if (msg.data.message.type === 'user') { if ((msg.data as AgentToolProgressData).message.type === 'user') {
if (info.isSearch) { if (info.isSearch) {
currentGroup.searchCount++ currentGroup.searchCount++
} else if (info.isREPL) { } else if (info.isREPL) {
@ -207,7 +230,7 @@ function processProgressMessages(
// Skip user tool_result messages — subagent progress messages lack // Skip user tool_result messages — subagent progress messages lack
// toolUseResult, so UserToolSuccessMessage returns null and the // toolUseResult, so UserToolSuccessMessage returns null and the
// height=1 Box in renderToolUseProgressMessage shows as a blank line. // height=1 Box in renderToolUseProgressMessage shows as a blank line.
if (msg.data.message.type !== 'user') { if ((msg.data as AgentToolProgressData).message.type !== 'user') {
result.push({ type: 'original', message: msg }) result.push({ type: 'original', message: msg })
} }
} }
@ -280,7 +303,7 @@ function VerboseAgentTranscript({
.filter((pm): pm is ProgressMessage<AgentToolProgress> => .filter((pm): pm is ProgressMessage<AgentToolProgress> =>
hasProgressMessage(pm.data), hasProgressMessage(pm.data),
) )
.map(pm => pm.data), .map(pm => pm.data as unknown as { message: AssistantMessage | UserMessage }),
) )
// Filter out user tool_result messages that lack toolUseResult. // Filter out user tool_result messages that lack toolUseResult.
@ -292,7 +315,7 @@ function VerboseAgentTranscript({
if (!hasProgressMessage(pm.data)) { if (!hasProgressMessage(pm.data)) {
return false return false
} }
const msg = pm.data.message const msg = pm.data.message as AgentProgressMessage
if (msg.type === 'user' && msg.toolUseResult === undefined) { if (msg.type === 'user' && msg.toolUseResult === undefined) {
return false return false
} }
@ -305,7 +328,7 @@ function VerboseAgentTranscript({
{filteredMessages.map(progressMessage => ( {filteredMessages.map(progressMessage => (
<MessageResponse key={progressMessage.uuid} height={1}> <MessageResponse key={progressMessage.uuid} height={1}>
<MessageComponent <MessageComponent
message={progressMessage.data.message} message={progressMessage.data.message as unknown as AssistantMessage | UserMessage}
lookups={agentLookups} lookups={agentLookups}
addMargin={false} addMargin={false}
tools={tools} tools={tools}
@ -555,7 +578,7 @@ export function renderToolUseProgressMessage(
if (!hasProgressMessage(msg.data)) { if (!hasProgressMessage(msg.data)) {
return false return false
} }
const message = msg.data.message const message = msg.data.message as AgentProgressMessage
return message.message.content.some( return message.message.content.some(
(content: BetaContentBlock) => content.type === 'tool_use', (content: BetaContentBlock) => content.type === 'tool_use',
) )
@ -563,12 +586,12 @@ export function renderToolUseProgressMessage(
const latestAssistant = progressMessages.findLast( const latestAssistant = progressMessages.findLast(
(msg): msg is ProgressMessage<AgentToolProgress> => (msg): msg is ProgressMessage<AgentToolProgress> =>
hasProgressMessage(msg.data) && msg.data.message.type === 'assistant', hasProgressMessage(msg.data) && (msg.data as AgentToolProgressData).message.type === 'assistant',
) )
let tokens = null let tokens = null
if (latestAssistant?.data.message.type === 'assistant') { if ((latestAssistant?.data as AgentToolProgressData)?.message.type === 'assistant') {
const usage = latestAssistant.data.message.message.usage const usage = (latestAssistant.data.message as AgentProgressMessage).message.usage
tokens = tokens =
(usage.cache_creation_input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) +
(usage.cache_read_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0) +
@ -631,14 +654,14 @@ export function renderToolUseProgressMessage(
if (!hasProgressMessage(data)) { if (!hasProgressMessage(data)) {
return false return false
} }
return data.message.message.content.some( return (data as AgentToolProgressData).message.message.content.some(
(content: BetaContentBlock) => content.type === 'tool_use', (content: BetaContentBlock) => content.type === 'tool_use',
) )
}) })
const firstData = progressMessages[0]?.data const firstData = progressMessages[0]?.data
const prompt = const prompt =
firstData && hasProgressMessage(firstData) ? firstData.prompt : undefined firstData && hasProgressMessage(firstData) ? (firstData as AgentToolProgressData).prompt : undefined
// After grouping, displayedMessages can be empty when the only progress so // After grouping, displayedMessages can be empty when the only progress so
// far is an assistant tool_use for a search/read op (grouped but not yet // far is an assistant tool_use for a search/read op (grouped but not yet
@ -660,7 +683,7 @@ export function renderToolUseProgressMessage(
.filter((pm): pm is ProgressMessage<AgentToolProgress> => .filter((pm): pm is ProgressMessage<AgentToolProgress> =>
hasProgressMessage(pm.data), hasProgressMessage(pm.data),
) )
.map(pm => pm.data), .map(pm => pm.data as unknown as { message: AssistantMessage | UserMessage }),
) )
return ( return (
@ -669,7 +692,7 @@ export function renderToolUseProgressMessage(
<SubAgentProvider> <SubAgentProvider>
{isTranscriptMode && prompt && ( {isTranscriptMode && prompt && (
<Box marginBottom={1}> <Box marginBottom={1}>
<AgentPromptDisplay prompt={prompt} /> <AgentPromptDisplay prompt={prompt as string} />
</Box> </Box>
)} )}
{displayedMessages.map(processed => { {displayedMessages.map(processed => {
@ -694,7 +717,7 @@ export function renderToolUseProgressMessage(
return ( return (
<MessageComponent <MessageComponent
key={processed.message.uuid} key={processed.message.uuid}
message={processed.message.data.message} message={processed.message.data.message as unknown as AssistantMessage | UserMessage}
lookups={subagentLookups} lookups={subagentLookups}
addMargin={false} addMargin={false}
tools={tools} tools={tools}
@ -743,14 +766,14 @@ export function renderToolUseRejectedMessage(
// Get agentId from progress messages if available (agent was running before rejection) // Get agentId from progress messages if available (agent was running before rejection)
const firstData = progressMessagesForMessage[0]?.data const firstData = progressMessagesForMessage[0]?.data
const agentId = const agentId =
firstData && hasProgressMessage(firstData) ? firstData.agentId : undefined firstData && hasProgressMessage(firstData) ? (firstData as AgentToolProgressData).agentId : undefined
return ( return (
<> <>
{process.env.USER_TYPE === 'ant' && agentId && ( {process.env.USER_TYPE === 'ant' && agentId && (
<MessageResponse> <MessageResponse>
<Text color="warning"> <Text color="warning">
[ANT-ONLY] API calls: {getDisplayPath(getDumpPromptsPath(agentId))} [ANT-ONLY] API calls: {getDisplayPath(getDumpPromptsPath(agentId as string))}
</Text> </Text>
</MessageResponse> </MessageResponse>
)} )}
@ -798,7 +821,7 @@ function calculateAgentStats(progressMessages: ProgressMessage<Progress>[]): {
if (!hasProgressMessage(msg.data)) { if (!hasProgressMessage(msg.data)) {
return false return false
} }
const message = msg.data.message const message = msg.data.message as AgentProgressMessage
return ( return (
message.type === 'user' && message.type === 'user' &&
message.message.content.some((content: BetaContentBlock) => content.type === 'tool_result') message.message.content.some((content: BetaContentBlock) => content.type === 'tool_result')
@ -807,12 +830,12 @@ function calculateAgentStats(progressMessages: ProgressMessage<Progress>[]): {
const latestAssistant = progressMessages.findLast( const latestAssistant = progressMessages.findLast(
(msg): msg is ProgressMessage<AgentToolProgress> => (msg): msg is ProgressMessage<AgentToolProgress> =>
hasProgressMessage(msg.data) && msg.data.message.type === 'assistant', hasProgressMessage(msg.data) && (msg.data as AgentToolProgressData).message.type === 'assistant',
) )
let tokens = null let tokens = null
if (latestAssistant?.data.message.type === 'assistant') { if ((latestAssistant?.data as AgentToolProgressData)?.message.type === 'assistant') {
const usage = latestAssistant.data.message.message.usage const usage = (latestAssistant.data.message as AgentProgressMessage).message.usage
tokens = tokens =
(usage.cache_creation_input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0) +
(usage.cache_read_input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0) +
@ -1035,8 +1058,8 @@ export function extractLastToolInfo(
if (!hasProgressMessage(pm.data)) { if (!hasProgressMessage(pm.data)) {
continue continue
} }
if (pm.data.message.type === 'assistant') { if ((pm.data as AgentToolProgressData).message.type === 'assistant') {
for (const c of pm.data.message.message.content) { for (const c of (pm.data as AgentToolProgressData).message.message.content) {
if (c.type === 'tool_use') { if (c.type === 'tool_use') {
toolUseByID.set(c.id, c as ToolUseBlockParam) toolUseByID.set(c.id, c as ToolUseBlockParam)
} }
@ -1055,7 +1078,7 @@ export function extractLastToolInfo(
const info = getSearchOrReadInfo(msg, tools, toolUseByID) const info = getSearchOrReadInfo(msg, tools, toolUseByID)
if (info && (info.isSearch || info.isRead)) { if (info && (info.isSearch || info.isRead)) {
// Only count tool_result messages to avoid double counting // Only count tool_result messages to avoid double counting
if (msg.data.message.type === 'user') { if ((msg.data as AgentToolProgressData).message.type === 'user') {
if (info.isSearch) { if (info.isSearch) {
searchCount++ searchCount++
} else if (info.isRead) { } else if (info.isRead) {
@ -1077,7 +1100,7 @@ export function extractLastToolInfo(
if (!hasProgressMessage(msg.data)) { if (!hasProgressMessage(msg.data)) {
return false return false
} }
const message = msg.data.message const message = msg.data.message as AgentProgressMessage
return ( return (
message.type === 'user' && message.type === 'user' &&
message.message.content.some((c: BetaContentBlock) => c.type === 'tool_result') message.message.content.some((c: BetaContentBlock) => c.type === 'tool_result')
@ -1085,8 +1108,8 @@ export function extractLastToolInfo(
}, },
) )
if (lastToolResult?.data.message.type === 'user') { if ((lastToolResult?.data.message as AgentProgressMessage)?.type === 'user') {
const toolResultBlock = lastToolResult.data.message.message.content.find( const toolResultBlock = (lastToolResult.data.message as AgentProgressMessage).message.content.find(
(c: BetaContentBlock) => c.type === 'tool_result', (c: BetaContentBlock) => c.type === 'tool_result',
) )