mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-22 16:25:51 +00:00
feat: 全部类型问题解决
This commit is contained in:
@@ -313,9 +313,9 @@ export function isSyntheticMessage(message: Message): boolean {
|
||||
message.type !== 'progress' &&
|
||||
message.type !== 'attachment' &&
|
||||
message.type !== 'system' &&
|
||||
Array.isArray(message.message.content) &&
|
||||
message.message.content[0]?.type === 'text' &&
|
||||
SYNTHETIC_MESSAGES.has(message.message.content[0].text)
|
||||
Array.isArray(message.message?.content) &&
|
||||
message.message?.content[0]?.type === 'text' &&
|
||||
SYNTHETIC_MESSAGES.has((message.message?.content[0] as { text: string }).text)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -325,7 +325,7 @@ function isSyntheticApiErrorMessage(
|
||||
return (
|
||||
message.type === 'assistant' &&
|
||||
message.isApiErrorMessage === true &&
|
||||
message.message.model === SYNTHETIC_MODEL
|
||||
message.message?.model === SYNTHETIC_MODEL
|
||||
)
|
||||
}
|
||||
|
||||
@@ -696,27 +696,30 @@ export function isNotEmptyMessage(message: Message): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
if (typeof message.message.content === 'string') {
|
||||
return message.message.content.trim().length > 0
|
||||
const msg = message.message
|
||||
if (!msg) return true
|
||||
|
||||
if (typeof msg.content === 'string') {
|
||||
return msg.content.trim().length > 0
|
||||
}
|
||||
|
||||
if (message.message.content.length === 0) {
|
||||
if (!msg.content || msg.content.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip multi-block messages for now
|
||||
if (message.message.content.length > 1) {
|
||||
if (msg.content.length > 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (message.message.content[0]!.type !== 'text') {
|
||||
if (msg.content[0]!.type !== 'text') {
|
||||
return true
|
||||
}
|
||||
|
||||
return (
|
||||
message.message.content[0]!.text.trim().length > 0 &&
|
||||
message.message.content[0]!.text !== NO_CONTENT_MESSAGE &&
|
||||
message.message.content[0]!.text !== INTERRUPT_MESSAGE_FOR_TOOL_USE
|
||||
(msg.content[0] as { text: string }).text.trim().length > 0 &&
|
||||
(msg.content[0] as { text: string }).text !== NO_CONTENT_MESSAGE &&
|
||||
(msg.content[0] as { text: string }).text !== INTERRUPT_MESSAGE_FOR_TOOL_USE
|
||||
)
|
||||
}
|
||||
|
||||
@@ -750,7 +753,8 @@ export function normalizeMessages(messages: Message[]): NormalizedMessage[] {
|
||||
return messages.flatMap(message => {
|
||||
switch (message.type) {
|
||||
case 'assistant': {
|
||||
const assistantContent = Array.isArray(message.message.content) ? message.message.content : []
|
||||
const aMsg = message as AssistantMessage
|
||||
const assistantContent = Array.isArray(aMsg.message.content) ? aMsg.message.content : []
|
||||
isNewChain = isNewChain || assistantContent.length > 1
|
||||
return assistantContent.map((_, index) => {
|
||||
const uuid = isNewChain
|
||||
@@ -760,9 +764,9 @@ export function normalizeMessages(messages: Message[]): NormalizedMessage[] {
|
||||
type: 'assistant' as const,
|
||||
timestamp: message.timestamp,
|
||||
message: {
|
||||
...message.message,
|
||||
...aMsg.message,
|
||||
content: [_],
|
||||
context_management: message.message.context_management ?? null,
|
||||
context_management: aMsg.message.context_management ?? null,
|
||||
},
|
||||
isMeta: message.isMeta,
|
||||
isVirtual: message.isVirtual,
|
||||
@@ -781,45 +785,48 @@ export function normalizeMessages(messages: Message[]): NormalizedMessage[] {
|
||||
case 'system':
|
||||
return [message]
|
||||
case 'user': {
|
||||
if (typeof message.message.content === 'string') {
|
||||
const uuid = isNewChain ? deriveUUID(message.uuid, 0) : message.uuid
|
||||
const uMsg = message as UserMessage
|
||||
if (typeof uMsg.message.content === 'string') {
|
||||
const uuid = isNewChain ? deriveUUID(uMsg.uuid, 0) : uMsg.uuid
|
||||
return [
|
||||
{
|
||||
...message,
|
||||
...uMsg,
|
||||
uuid,
|
||||
message: {
|
||||
...message.message,
|
||||
content: [{ type: 'text', text: message.message.content }],
|
||||
...uMsg.message,
|
||||
content: [{ type: 'text', text: uMsg.message.content }],
|
||||
},
|
||||
} as NormalizedMessage,
|
||||
]
|
||||
}
|
||||
isNewChain = isNewChain || message.message.content.length > 1
|
||||
isNewChain = isNewChain || (uMsg.message.content?.length ?? 0) > 1
|
||||
let imageIndex = 0
|
||||
return message.message.content.map((_, index) => {
|
||||
return (uMsg.message.content ?? []).map((_, index) => {
|
||||
const isImage = _.type === 'image'
|
||||
// For image content blocks, extract just the ID for this image
|
||||
const imageId =
|
||||
isImage && message.imagePasteIds
|
||||
? message.imagePasteIds[imageIndex]
|
||||
isImage && uMsg.imagePasteIds
|
||||
? (uMsg.imagePasteIds as number[])[imageIndex]
|
||||
: undefined
|
||||
if (isImage) imageIndex++
|
||||
return {
|
||||
...createUserMessage({
|
||||
content: [_],
|
||||
toolUseResult: message.toolUseResult,
|
||||
mcpMeta: message.mcpMeta as { _meta?: Record<string, unknown>; structuredContent?: Record<string, unknown> },
|
||||
isMeta: message.isMeta === true ? true : undefined,
|
||||
isVisibleInTranscriptOnly: message.isVisibleInTranscriptOnly === true ? true : undefined,
|
||||
isVirtual: (message.isVirtual as boolean | undefined) === true ? true : undefined,
|
||||
timestamp: message.timestamp as string | undefined,
|
||||
toolUseResult: uMsg.toolUseResult,
|
||||
mcpMeta: uMsg.mcpMeta as { _meta?: Record<string, unknown>; structuredContent?: Record<string, unknown> },
|
||||
isMeta: uMsg.isMeta === true ? true : undefined,
|
||||
isVisibleInTranscriptOnly: uMsg.isVisibleInTranscriptOnly === true ? true : undefined,
|
||||
isVirtual: (uMsg.isVirtual as boolean | undefined) === true ? true : undefined,
|
||||
timestamp: uMsg.timestamp as string | undefined,
|
||||
imagePasteIds: imageId !== undefined ? [imageId] : undefined,
|
||||
origin: message.origin as MessageOrigin | undefined,
|
||||
origin: uMsg.origin as MessageOrigin | undefined,
|
||||
}),
|
||||
uuid: isNewChain ? deriveUUID(message.uuid, index) : message.uuid,
|
||||
uuid: isNewChain ? deriveUUID(uMsg.uuid, index) : uMsg.uuid,
|
||||
} as NormalizedMessage
|
||||
})
|
||||
}
|
||||
default:
|
||||
return [message]
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -834,8 +841,8 @@ export function isToolUseRequestMessage(
|
||||
return (
|
||||
message.type === 'assistant' &&
|
||||
// Note: stop_reason === 'tool_use' is unreliable -- it's not always set correctly
|
||||
Array.isArray(message.message.content) &&
|
||||
message.message.content.some(_ => _.type === 'tool_use')
|
||||
Array.isArray(message.message?.content) &&
|
||||
(message.message?.content as Array<{type: string}>).some(_ => _.type === 'tool_use')
|
||||
)
|
||||
}
|
||||
|
||||
@@ -848,8 +855,8 @@ export function isToolUseResultMessage(
|
||||
): message is ToolUseResultMessage {
|
||||
return (
|
||||
message.type === 'user' &&
|
||||
((Array.isArray(message.message.content) &&
|
||||
message.message.content[0]?.type === 'tool_result') ||
|
||||
((Array.isArray(message.message?.content) &&
|
||||
(message.message?.content as Array<{type: string}>)[0]?.type === 'tool_result') ||
|
||||
Boolean(message.toolUseResult))
|
||||
)
|
||||
}
|
||||
@@ -1035,14 +1042,14 @@ function isHookAttachmentMessage(
|
||||
): message is AttachmentMessage<HookAttachment> {
|
||||
return (
|
||||
message.type === 'attachment' &&
|
||||
(message.attachment.type === 'hook_blocking_error' ||
|
||||
message.attachment.type === 'hook_cancelled' ||
|
||||
message.attachment.type === 'hook_error_during_execution' ||
|
||||
message.attachment.type === 'hook_non_blocking_error' ||
|
||||
message.attachment.type === 'hook_success' ||
|
||||
message.attachment.type === 'hook_system_message' ||
|
||||
message.attachment.type === 'hook_additional_context' ||
|
||||
message.attachment.type === 'hook_stopped_continuation')
|
||||
(message.attachment?.type === 'hook_blocking_error' ||
|
||||
message.attachment?.type === 'hook_cancelled' ||
|
||||
message.attachment?.type === 'hook_error_during_execution' ||
|
||||
message.attachment?.type === 'hook_non_blocking_error' ||
|
||||
message.attachment?.type === 'hook_success' ||
|
||||
message.attachment?.type === 'hook_system_message' ||
|
||||
message.attachment?.type === 'hook_additional_context' ||
|
||||
message.attachment?.type === 'hook_stopped_continuation')
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1105,11 +1112,11 @@ export function getToolResultIDs(normalizedMessages: NormalizedMessage[]): {
|
||||
} {
|
||||
return Object.fromEntries(
|
||||
normalizedMessages.flatMap(_ =>
|
||||
_.type === 'user' && Array.isArray(_.message.content) && _.message.content[0]?.type === 'tool_result'
|
||||
_.type === 'user' && Array.isArray(_.message?.content) && (_.message?.content as Array<{type:string}>)[0]?.type === 'tool_result'
|
||||
? [
|
||||
[
|
||||
(_.message.content[0] as ToolResultBlockParam).tool_use_id,
|
||||
(_.message.content[0] as ToolResultBlockParam).is_error ?? false,
|
||||
((_.message?.content as Array<{type:string}>)[0] as ToolResultBlockParam).tool_use_id,
|
||||
((_.message?.content as Array<{type:string}>)[0] as ToolResultBlockParam).is_error ?? false,
|
||||
],
|
||||
]
|
||||
: ([] as [string, boolean][]),
|
||||
@@ -1129,8 +1136,8 @@ export function getSiblingToolUseIDs(
|
||||
const unnormalizedMessage = messages.find(
|
||||
(_): _ is AssistantMessage =>
|
||||
_.type === 'assistant' &&
|
||||
Array.isArray(_.message.content) &&
|
||||
_.message.content.some(block => block.type === 'tool_use' && (block as ToolUseBlock).id === toolUseID),
|
||||
Array.isArray(_.message?.content) &&
|
||||
(_.message?.content as Array<{type:string; id?:string}>).some(block => block.type === 'tool_use' && block.id === toolUseID),
|
||||
)
|
||||
if (!unnormalizedMessage) {
|
||||
return new Set()
|
||||
@@ -1139,13 +1146,13 @@ export function getSiblingToolUseIDs(
|
||||
const messageID = unnormalizedMessage.message.id
|
||||
const siblingMessages = messages.filter(
|
||||
(_): _ is AssistantMessage =>
|
||||
_.type === 'assistant' && _.message.id === messageID,
|
||||
_.type === 'assistant' && _.message?.id === messageID,
|
||||
)
|
||||
|
||||
return new Set(
|
||||
siblingMessages.flatMap(_ =>
|
||||
Array.isArray(_.message.content)
|
||||
? _.message.content.filter(_ => _.type === 'tool_use').map(_ => (_ as ToolUseBlock).id)
|
||||
Array.isArray(_.message?.content)
|
||||
? (_.message?.content as Array<{type:string; id?:string}>).filter(_ => _.type === 'tool_use').map(_ => _.id!)
|
||||
: [],
|
||||
),
|
||||
)
|
||||
@@ -1185,14 +1192,15 @@ export function buildMessageLookups(
|
||||
const toolUseByToolUseID = new Map<string, ToolUseBlockParam>()
|
||||
for (const msg of messages) {
|
||||
if (msg.type === 'assistant') {
|
||||
const id = msg.message.id
|
||||
const aMsg = msg as AssistantMessage
|
||||
const id = aMsg.message.id!
|
||||
let toolUseIDs = toolUseIDsByMessageID.get(id)
|
||||
if (!toolUseIDs) {
|
||||
toolUseIDs = new Set()
|
||||
toolUseIDsByMessageID.set(id, toolUseIDs)
|
||||
}
|
||||
if (Array.isArray(msg.message.content)) {
|
||||
for (const content of msg.message.content) {
|
||||
if (Array.isArray(aMsg.message.content)) {
|
||||
for (const content of aMsg.message.content) {
|
||||
if (typeof content !== 'string' && content.type === 'tool_use') {
|
||||
const toolUseContent = content as ToolUseBlock
|
||||
toolUseIDs.add(toolUseContent.id)
|
||||
@@ -1247,8 +1255,8 @@ export function buildMessageLookups(
|
||||
}
|
||||
|
||||
// Build tool result lookup and resolved/errored sets
|
||||
if (msg.type === 'user' && Array.isArray(msg.message.content)) {
|
||||
for (const content of msg.message.content) {
|
||||
if (msg.type === 'user' && Array.isArray(msg.message?.content)) {
|
||||
for (const content of (msg.message?.content ?? [])) {
|
||||
if (typeof content !== 'string' && content.type === 'tool_result') {
|
||||
const tr = content as ToolResultBlockParam
|
||||
toolResultByToolUseID.set(tr.tool_use_id, msg)
|
||||
@@ -1260,8 +1268,8 @@ export function buildMessageLookups(
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.type === 'assistant' && Array.isArray(msg.message.content)) {
|
||||
for (const content of msg.message.content) {
|
||||
if (msg.type === 'assistant' && Array.isArray(msg.message?.content)) {
|
||||
for (const content of (msg.message?.content ?? [])) {
|
||||
if (typeof content === 'string') continue
|
||||
// Track all server-side *_tool_result blocks (advisor, web_search,
|
||||
// code_execution, mcp, etc.) — any block with tool_use_id is a result.
|
||||
@@ -1321,14 +1329,15 @@ export function buildMessageLookups(
|
||||
// perpetually spinning.
|
||||
const lastMsg = messages.at(-1)
|
||||
const lastAssistantMsgId =
|
||||
lastMsg?.type === 'assistant' ? lastMsg.message.id : undefined
|
||||
lastMsg?.type === 'assistant' ? lastMsg.message?.id : undefined
|
||||
for (const msg of normalizedMessages) {
|
||||
if (msg.type !== 'assistant') continue
|
||||
const aMsg = msg as AssistantMessage
|
||||
// Skip blocks from the last original message if it's an assistant,
|
||||
// since it may still be in progress.
|
||||
if (msg.message.id === lastAssistantMsgId) continue
|
||||
if (!Array.isArray(msg.message.content)) continue
|
||||
for (const content of msg.message.content) {
|
||||
if (aMsg.message.id === lastAssistantMsgId) continue
|
||||
if (!Array.isArray(aMsg.message.content)) continue
|
||||
for (const content of aMsg.message.content) {
|
||||
if (
|
||||
typeof content !== 'string' &&
|
||||
((content.type as string) === 'server_tool_use' ||
|
||||
@@ -1483,10 +1492,10 @@ export function getToolUseIDs(
|
||||
.filter(
|
||||
(_): _ is NormalizedAssistantMessage<BetaToolUseBlock> =>
|
||||
_.type === 'assistant' &&
|
||||
Array.isArray(_.message.content) &&
|
||||
_.message.content[0]?.type === 'tool_use',
|
||||
Array.isArray(_.message?.content) &&
|
||||
(_.message?.content as Array<{type:string}>)[0]?.type === 'tool_use',
|
||||
)
|
||||
.map(_ => (_.message.content[0] as BetaToolUseBlock).id),
|
||||
.map(_ => ((_.message?.content as Array<BetaToolUseBlock>)[0]).id),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1515,8 +1524,8 @@ export function reorderAttachmentsForAPI(messages: Message[]): Message[] {
|
||||
const isStoppingPoint =
|
||||
message.type === 'assistant' ||
|
||||
(message.type === 'user' &&
|
||||
Array.isArray(message.message.content) &&
|
||||
message.message.content[0]?.type === 'tool_result')
|
||||
Array.isArray(message.message?.content) &&
|
||||
(message.message?.content as Array<{type:string}>)[0]?.type === 'tool_result')
|
||||
|
||||
if (isStoppingPoint && pendingAttachments.length > 0) {
|
||||
// Hit a stopping point — attachments stop here (go after the stopping point).
|
||||
@@ -1815,6 +1824,7 @@ function contentHasToolReference(
|
||||
*/
|
||||
function ensureSystemReminderWrap(msg: UserMessage): UserMessage {
|
||||
const content = msg.message.content
|
||||
if (!content) return msg
|
||||
if (typeof content === 'string') {
|
||||
if (content.startsWith('<system-reminder>')) return msg
|
||||
return {
|
||||
@@ -2397,8 +2407,8 @@ export function mergeUserMessagesAndToolResults(
|
||||
a: UserMessage,
|
||||
b: UserMessage,
|
||||
): UserMessage {
|
||||
const lastContent = normalizeUserTextContent(a.message.content)
|
||||
const currentContent = normalizeUserTextContent(b.message.content)
|
||||
const lastContent = normalizeUserTextContent(a.message.content as string | ContentBlockParam[])
|
||||
const currentContent = normalizeUserTextContent(b.message.content as string | ContentBlockParam[])
|
||||
return {
|
||||
...a,
|
||||
message: {
|
||||
@@ -2430,14 +2440,14 @@ function isToolResultMessage(msg: Message): boolean {
|
||||
if (msg.type !== 'user') {
|
||||
return false
|
||||
}
|
||||
const content = msg.message.content
|
||||
if (typeof content === 'string') return false
|
||||
return content.some(block => block.type === 'tool_result')
|
||||
const content = msg.message?.content
|
||||
if (!content || typeof content === 'string') return false
|
||||
return (content as Array<{type:string}>).some(block => block.type === 'tool_result')
|
||||
}
|
||||
|
||||
export function mergeUserMessages(a: UserMessage, b: UserMessage): UserMessage {
|
||||
const lastContent = normalizeUserTextContent(a.message.content)
|
||||
const currentContent = normalizeUserTextContent(b.message.content)
|
||||
const lastContent = normalizeUserTextContent(a.message.content as string | ContentBlockParam[])
|
||||
const currentContent = normalizeUserTextContent(b.message.content as string | ContentBlockParam[])
|
||||
if (feature('HISTORY_SNIP')) {
|
||||
// A merged message is only meta if ALL merged messages are meta. If any
|
||||
// operand is real user content, the result must not be flagged isMeta
|
||||
@@ -2793,12 +2803,12 @@ export function getToolUseID(message: NormalizedMessage): string | null {
|
||||
switch (message.type) {
|
||||
case 'attachment':
|
||||
if (isHookAttachmentMessage(message)) {
|
||||
return message.attachment.toolUseID
|
||||
return message.attachment.toolUseID ?? null
|
||||
}
|
||||
return null
|
||||
case 'assistant': {
|
||||
const aContent = Array.isArray(message.message.content) ? message.message.content : []
|
||||
const firstBlock = aContent[0]
|
||||
const aContent = Array.isArray(message.message?.content) ? message.message?.content : []
|
||||
const firstBlock = aContent![0]
|
||||
if (!firstBlock || typeof firstBlock === 'string' || firstBlock.type !== 'tool_use') {
|
||||
return null
|
||||
}
|
||||
@@ -2808,8 +2818,8 @@ export function getToolUseID(message: NormalizedMessage): string | null {
|
||||
if (message.sourceToolUseID) {
|
||||
return message.sourceToolUseID as string
|
||||
}
|
||||
const uContent = Array.isArray(message.message.content) ? message.message.content : []
|
||||
const firstUBlock = uContent[0]
|
||||
const uContent = Array.isArray(message.message?.content) ? message.message?.content : []
|
||||
const firstUBlock = uContent![0]
|
||||
if (!firstUBlock || typeof firstUBlock === 'string' || firstUBlock.type !== 'tool_result') {
|
||||
return null
|
||||
}
|
||||
@@ -2821,6 +2831,8 @@ export function getToolUseID(message: NormalizedMessage): string | null {
|
||||
return (message.subtype as string) === 'informational'
|
||||
? ((message.toolUseID as string) ?? null)
|
||||
: null
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2835,14 +2847,14 @@ export function filterUnresolvedToolUses(messages: Message[]): Message[] {
|
||||
|
||||
for (const msg of messages) {
|
||||
if (msg.type !== 'user' && msg.type !== 'assistant') continue
|
||||
const content = msg.message.content
|
||||
const content = msg.message?.content
|
||||
if (!Array.isArray(content)) continue
|
||||
for (const block of content) {
|
||||
for (const block of content as Array<{type:string; id?:string; tool_use_id?:string}>) {
|
||||
if (block.type === 'tool_use') {
|
||||
toolUseIds.add(block.id)
|
||||
toolUseIds.add(block.id!)
|
||||
}
|
||||
if (block.type === 'tool_result') {
|
||||
toolResultIds.add(block.tool_use_id)
|
||||
toolResultIds.add(block.tool_use_id!)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2858,12 +2870,12 @@ export function filterUnresolvedToolUses(messages: Message[]): Message[] {
|
||||
// Filter out assistant messages whose tool_use blocks are all unresolved
|
||||
return messages.filter(msg => {
|
||||
if (msg.type !== 'assistant') return true
|
||||
const content = msg.message.content
|
||||
const content = msg.message?.content
|
||||
if (!Array.isArray(content)) return true
|
||||
const toolUseBlockIds: string[] = []
|
||||
for (const b of content) {
|
||||
for (const b of content as Array<{type:string; id?:string}>) {
|
||||
if (b.type === 'tool_use') {
|
||||
toolUseBlockIds.push(b.id)
|
||||
toolUseBlockIds.push(b.id!)
|
||||
}
|
||||
}
|
||||
if (toolUseBlockIds.length === 0) return true
|
||||
@@ -2878,11 +2890,11 @@ export function getAssistantMessageText(message: Message): string | null {
|
||||
}
|
||||
|
||||
// For content blocks array, extract and concatenate text blocks
|
||||
if (Array.isArray(message.message.content)) {
|
||||
if (Array.isArray(message.message?.content)) {
|
||||
return (
|
||||
message.message.content
|
||||
(message.message?.content as Array<{type:string; text?:string}>)
|
||||
.filter(block => block.type === 'text')
|
||||
.map(block => (block.type === 'text' ? block.text : ''))
|
||||
.map(block => block.text ?? '')
|
||||
.join('\n')
|
||||
.trim() || null
|
||||
)
|
||||
@@ -2897,9 +2909,9 @@ export function getUserMessageText(
|
||||
return null
|
||||
}
|
||||
|
||||
const content = message.message.content
|
||||
const content = message.message?.content
|
||||
|
||||
return getContentText(content)
|
||||
return getContentText(content as string | ContentBlockParam[])
|
||||
}
|
||||
|
||||
export function textForResubmit(
|
||||
@@ -4462,7 +4474,7 @@ export function createStopHookSummaryMessage(
|
||||
timestamp: new Date().toISOString(),
|
||||
uuid: randomUUID(),
|
||||
toolUseID,
|
||||
hookLabel,
|
||||
hookLabel: hookLabel ?? '',
|
||||
totalDurationMs,
|
||||
}
|
||||
}
|
||||
@@ -4720,8 +4732,8 @@ export function shouldShowUserMessage(
|
||||
|
||||
export function isThinkingMessage(message: Message): boolean {
|
||||
if (message.type !== 'assistant') return false
|
||||
if (!Array.isArray(message.message.content)) return false
|
||||
return message.message.content.every(
|
||||
if (!Array.isArray(message.message?.content)) return false
|
||||
return (message.message?.content as Array<{type:string}>).every(
|
||||
block => block.type === 'thinking' || block.type === 'redacted_thinking',
|
||||
)
|
||||
}
|
||||
@@ -4738,8 +4750,8 @@ export function countToolCalls(
|
||||
let count = 0
|
||||
for (const msg of messages) {
|
||||
if (!msg) continue
|
||||
if (msg.type === 'assistant' && Array.isArray(msg.message.content)) {
|
||||
const hasToolUse = msg.message.content.some(
|
||||
if (msg.type === 'assistant' && Array.isArray(msg.message?.content)) {
|
||||
const hasToolUse = (msg.message?.content as Array<{type:string; name?:string}>).some(
|
||||
(block): block is ToolUseBlock =>
|
||||
block.type === 'tool_use' && block.name === toolName,
|
||||
)
|
||||
@@ -4767,8 +4779,8 @@ export function hasSuccessfulToolCall(
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const msg = messages[i]
|
||||
if (!msg) continue
|
||||
if (msg.type === 'assistant' && Array.isArray(msg.message.content)) {
|
||||
const toolUse = msg.message.content.find(
|
||||
if (msg.type === 'assistant' && Array.isArray(msg.message?.content)) {
|
||||
const toolUse = (msg.message?.content as Array<{type:string; name?:string; id?:string}>).find(
|
||||
(block): block is ToolUseBlock =>
|
||||
block.type === 'tool_use' && block.name === toolName,
|
||||
)
|
||||
@@ -4785,8 +4797,8 @@ export function hasSuccessfulToolCall(
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const msg = messages[i]
|
||||
if (!msg) continue
|
||||
if (msg.type === 'user' && Array.isArray(msg.message.content)) {
|
||||
const toolResult = msg.message.content.find(
|
||||
if (msg.type === 'user' && Array.isArray(msg.message?.content)) {
|
||||
const toolResult = (msg.message?.content as Array<{type:string; tool_use_id?:string; is_error?:boolean}>).find(
|
||||
(block): block is ToolResultBlockParam =>
|
||||
block.type === 'tool_result' &&
|
||||
block.tool_use_id === mostRecentToolUseId,
|
||||
@@ -4925,8 +4937,7 @@ export function filterWhitespaceOnlyAssistantMessages(
|
||||
return true
|
||||
}
|
||||
|
||||
const content = message.message.content
|
||||
// Keep messages with empty arrays (handled elsewhere) or that have real content
|
||||
const content = message.message?.content
|
||||
if (!Array.isArray(content) || content.length === 0) {
|
||||
return true
|
||||
}
|
||||
@@ -5046,14 +5057,14 @@ export function filterOrphanedThinkingOnlyMessages(
|
||||
for (const msg of messages) {
|
||||
if (msg.type !== 'assistant') continue
|
||||
|
||||
const content = msg.message.content
|
||||
const content = msg.message?.content
|
||||
if (!Array.isArray(content)) continue
|
||||
|
||||
const hasNonThinking = content.some(
|
||||
const hasNonThinking = (content as Array<{type:string}>).some(
|
||||
block => block.type !== 'thinking' && block.type !== 'redacted_thinking',
|
||||
)
|
||||
if (hasNonThinking && msg.message.id) {
|
||||
messageIdsWithNonThinkingContent.add(msg.message.id)
|
||||
if (hasNonThinking && msg.message?.id) {
|
||||
messageIdsWithNonThinkingContent.add(msg.message.id as string)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5063,13 +5074,13 @@ export function filterOrphanedThinkingOnlyMessages(
|
||||
return true
|
||||
}
|
||||
|
||||
const content = msg.message.content
|
||||
const content = msg.message?.content
|
||||
if (!Array.isArray(content) || content.length === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if ALL content blocks are thinking blocks
|
||||
const allThinking = content.every(
|
||||
const allThinking = (content as Array<{type:string}>).every(
|
||||
block => block.type === 'thinking' || block.type === 'redacted_thinking',
|
||||
)
|
||||
|
||||
@@ -5080,8 +5091,8 @@ export function filterOrphanedThinkingOnlyMessages(
|
||||
// It's thinking-only. Keep it if there's another message with same id
|
||||
// that has non-thinking content (they'll be merged later)
|
||||
if (
|
||||
msg.message.id &&
|
||||
messageIdsWithNonThinkingContent.has(msg.message.id)
|
||||
msg.message?.id &&
|
||||
messageIdsWithNonThinkingContent.has(msg.message.id as string)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
@@ -5091,7 +5102,7 @@ export function filterOrphanedThinkingOnlyMessages(
|
||||
messageUUID:
|
||||
msg.uuid as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
messageId: msg.message
|
||||
.id as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
?.id as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||
blockCount: content.length,
|
||||
})
|
||||
return false
|
||||
@@ -5111,7 +5122,7 @@ export function stripSignatureBlocks(messages: Message[]): Message[] {
|
||||
const result = messages.map(msg => {
|
||||
if (msg.type !== 'assistant') return msg
|
||||
|
||||
const content = msg.message.content
|
||||
const content = (msg as AssistantMessage).message.content
|
||||
if (!Array.isArray(content)) return msg
|
||||
|
||||
const filtered = content.filter(block => {
|
||||
@@ -5247,7 +5258,8 @@ export function ensureToolResultPairing(
|
||||
|
||||
// Collect server-side tool result IDs (*_tool_result blocks have tool_use_id).
|
||||
const serverResultIds = new Set<string>()
|
||||
for (const c of msg.message.content) {
|
||||
const aMsg5 = msg as AssistantMessage
|
||||
for (const c of aMsg5.message.content as (ContentBlockParam | ContentBlock)[]) {
|
||||
if (typeof c !== 'string' && 'tool_use_id' in c && typeof (c as { tool_use_id: string }).tool_use_id === 'string') {
|
||||
serverResultIds.add((c as { tool_use_id: string }).tool_use_id)
|
||||
}
|
||||
@@ -5266,7 +5278,7 @@ export function ensureToolResultPairing(
|
||||
// has no matching *_tool_result and the API rejects with e.g. "advisor
|
||||
// tool use without corresponding advisor_tool_result".
|
||||
const seenToolUseIds = new Set<string>()
|
||||
const assistantContent = Array.isArray(msg.message.content) ? msg.message.content : []
|
||||
const assistantContent = Array.isArray(aMsg5.message.content) ? aMsg5.message.content : []
|
||||
const finalContent = assistantContent.filter(block => {
|
||||
if (typeof block === 'string') return true
|
||||
if (block.type === 'tool_use') {
|
||||
@@ -5288,7 +5300,7 @@ export function ensureToolResultPairing(
|
||||
})
|
||||
|
||||
const assistantContentChanged =
|
||||
finalContent.length !== msg.message.content.length
|
||||
finalContent.length !== (aMsg5.message.content as (ContentBlockParam | ContentBlock)[]).length
|
||||
|
||||
// If stripping orphaned server tool uses empties the content array,
|
||||
// insert a placeholder so the API doesn't reject empty assistant content.
|
||||
@@ -5372,11 +5384,12 @@ export function ensureToolResultPairing(
|
||||
|
||||
if (nextMsg?.type === 'user') {
|
||||
// Next message is already a user message - patch it
|
||||
const nextUserMsg = nextMsg as UserMessage
|
||||
let content: (ContentBlockParam | ContentBlock)[] = Array.isArray(
|
||||
nextMsg.message.content,
|
||||
nextUserMsg.message.content,
|
||||
)
|
||||
? nextMsg.message.content
|
||||
: [{ type: 'text' as const, text: nextMsg.message.content }]
|
||||
? nextUserMsg.message.content as (ContentBlockParam | ContentBlock)[]
|
||||
: [{ type: 'text' as const, text: (nextUserMsg.message.content as string | undefined) ?? '' }]
|
||||
|
||||
// Strip orphaned tool_results and dedupe duplicate tool_result IDs
|
||||
if (orphanedIds.length > 0 || hasDuplicateToolResults) {
|
||||
@@ -5402,9 +5415,9 @@ export function ensureToolResultPairing(
|
||||
// If content is now empty after stripping orphans, skip the user message
|
||||
if (patchedContent.length > 0) {
|
||||
const patchedNext: UserMessage = {
|
||||
...nextMsg,
|
||||
...nextUserMsg,
|
||||
message: {
|
||||
...nextMsg.message,
|
||||
...nextUserMsg.message,
|
||||
content: patchedContent,
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user