mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 14:25:51 +00:00
feat: 全部类型问题解决
This commit is contained in:
@@ -86,9 +86,9 @@ describe("createAssistantMessage", () => {
|
||||
test("creates assistant message with string content", () => {
|
||||
const msg = createAssistantMessage({ content: "hello" });
|
||||
expect(msg.type).toBe("assistant");
|
||||
expect(msg.message.role).toBe("assistant");
|
||||
expect(msg.message.content).toHaveLength(1);
|
||||
expect((msg.message.content[0] as any).text).toBe("hello");
|
||||
expect(msg.message!.role).toBe("assistant");
|
||||
expect(msg.message!.content![0] as any).toBeTruthy();
|
||||
expect((msg.message!.content![0] as any).text).toBe("hello");
|
||||
});
|
||||
|
||||
test("creates assistant message with content blocks", () => {
|
||||
@@ -501,7 +501,7 @@ describe("normalizeMessagesForAPI", () => {
|
||||
]);
|
||||
|
||||
const normalized = normalizeMessagesForAPI([assistant]);
|
||||
const block = (normalized[0] as AssistantMessage).message.content[0] as any;
|
||||
const block = (normalized[0] as AssistantMessage).message!.content![0] as any;
|
||||
|
||||
expect(block.type).toBe("tool_use");
|
||||
expect(block._geminiThoughtSignature).toBe("sig-123");
|
||||
|
||||
@@ -445,8 +445,8 @@ async function countBuiltInToolTokens(
|
||||
if (messages) {
|
||||
const deferredToolNameSet = new Set(deferredBuiltinTools.map(t => t.name))
|
||||
for (const msg of messages) {
|
||||
if (msg.type === 'assistant' && Array.isArray(msg.message.content)) {
|
||||
for (const block of msg.message.content) {
|
||||
if (msg.type === 'assistant' && Array.isArray(msg.message!.content)) {
|
||||
for (const block of msg.message!.content) {
|
||||
if (
|
||||
typeof block !== 'string' &&
|
||||
'type' in block &&
|
||||
@@ -683,8 +683,8 @@ export async function countMcpToolTokens(
|
||||
if (isDeferred && messages) {
|
||||
const mcpToolNameSet = new Set(mcpTools.map(t => t.name))
|
||||
for (const msg of messages) {
|
||||
if (msg.type === 'assistant' && Array.isArray(msg.message.content)) {
|
||||
for (const block of msg.message.content) {
|
||||
if (msg.type === 'assistant' && Array.isArray(msg.message!.content)) {
|
||||
for (const block of msg.message!.content) {
|
||||
if (
|
||||
typeof block !== 'string' &&
|
||||
'type' in block &&
|
||||
@@ -786,7 +786,7 @@ function processAssistantMessage(
|
||||
breakdown: MessageBreakdown,
|
||||
): void {
|
||||
// Process each content block individually
|
||||
const contentBlocks = Array.isArray(msg.message.content) ? msg.message.content : []
|
||||
const contentBlocks = Array.isArray(msg.message!.content) ? msg.message!.content : []
|
||||
for (const block of contentBlocks) {
|
||||
const blockStr = jsonStringify(block)
|
||||
const blockTokens = roughTokenCountEstimation(blockStr)
|
||||
@@ -811,20 +811,19 @@ function processUserMessage(
|
||||
toolUseIdToName: Map<string, string>,
|
||||
): void {
|
||||
// Handle both string and array content
|
||||
if (typeof msg.message.content === 'string') {
|
||||
if (typeof msg.message!.content === 'string') {
|
||||
// Simple string content
|
||||
const tokens = roughTokenCountEstimation(msg.message.content)
|
||||
const tokens = roughTokenCountEstimation(msg.message!.content)
|
||||
breakdown.userMessageTokens += tokens
|
||||
return
|
||||
}
|
||||
|
||||
// Process each content block individually
|
||||
for (const block of msg.message.content) {
|
||||
for (const block of (msg.message!.content ?? [])) {
|
||||
const blockStr = jsonStringify(block)
|
||||
const blockTokens = roughTokenCountEstimation(blockStr)
|
||||
|
||||
if ('type' in block && block.type === 'tool_result') {
|
||||
breakdown.toolResultTokens += blockTokens
|
||||
const toolUseId = 'tool_use_id' in block ? block.tool_use_id : undefined
|
||||
const toolName =
|
||||
(toolUseId ? toolUseIdToName.get(toolUseId) : undefined) || 'unknown'
|
||||
@@ -874,8 +873,8 @@ async function approximateMessageTokens(
|
||||
// Build a map of tool_use_id to tool_name for easier lookup
|
||||
const toolUseIdToName = new Map<string, string>()
|
||||
for (const msg of microcompactResult.messages) {
|
||||
if (msg.type === 'assistant' && Array.isArray(msg.message.content)) {
|
||||
for (const block of msg.message.content) {
|
||||
if (msg.type === 'assistant' && Array.isArray(msg.message!.content)) {
|
||||
for (const block of msg.message!.content) {
|
||||
if (typeof block !== 'string' && 'type' in block && block.type === 'tool_use') {
|
||||
const toolUseId = 'id' in block ? (block.id as string) : undefined
|
||||
const toolName =
|
||||
|
||||
@@ -193,8 +193,8 @@ export function installAsciicastRecorder(): void {
|
||||
) as typeof process.stdout.write
|
||||
process.stdout.write = function (
|
||||
chunk: string | Uint8Array,
|
||||
encodingOrCb?: BufferEncoding | ((err?: Error) => void),
|
||||
cb?: (err?: Error) => void,
|
||||
encodingOrCb?: BufferEncoding | ((err?: Error | null) => void),
|
||||
cb?: (err?: Error | null) => void,
|
||||
): boolean {
|
||||
// Record the output event
|
||||
const elapsed = (performance.now() - startTime) / 1000
|
||||
|
||||
@@ -1147,13 +1147,13 @@ function getPlanModeAttachmentTurnCount(messages: Message[]): {
|
||||
if (
|
||||
message?.type === 'user' &&
|
||||
!message.isMeta &&
|
||||
!hasToolResultContent(message.message.content)
|
||||
!hasToolResultContent(message.message!.content)
|
||||
) {
|
||||
turnsSinceLastAttachment++
|
||||
} else if (
|
||||
message?.type === 'attachment' &&
|
||||
(message.attachment.type === 'plan_mode' ||
|
||||
message.attachment.type === 'plan_mode_reentry')
|
||||
(message.attachment!.type === 'plan_mode' ||
|
||||
message.attachment!.type === 'plan_mode_reentry')
|
||||
) {
|
||||
foundPlanModeAttachment = true
|
||||
break
|
||||
@@ -1173,10 +1173,10 @@ function countPlanModeAttachmentsSinceLastExit(messages: Message[]): number {
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const message = messages[i]
|
||||
if (message?.type === 'attachment') {
|
||||
if (message.attachment.type === 'plan_mode_exit') {
|
||||
if (message.attachment!.type === 'plan_mode_exit') {
|
||||
break // Stop counting at the last exit
|
||||
}
|
||||
if (message.attachment.type === 'plan_mode') {
|
||||
if (message.attachment!.type === 'plan_mode') {
|
||||
count++
|
||||
}
|
||||
}
|
||||
@@ -1292,18 +1292,18 @@ function getAutoModeAttachmentTurnCount(messages: Message[]): {
|
||||
if (
|
||||
message?.type === 'user' &&
|
||||
!message.isMeta &&
|
||||
!hasToolResultContent(message.message.content)
|
||||
!hasToolResultContent(message.message!.content)
|
||||
) {
|
||||
turnsSinceLastAttachment++
|
||||
} else if (
|
||||
message?.type === 'attachment' &&
|
||||
message.attachment.type === 'auto_mode'
|
||||
message.attachment!.type === 'auto_mode'
|
||||
) {
|
||||
foundAutoModeAttachment = true
|
||||
break
|
||||
} else if (
|
||||
message?.type === 'attachment' &&
|
||||
message.attachment.type === 'auto_mode_exit'
|
||||
message.attachment!.type === 'auto_mode_exit'
|
||||
) {
|
||||
// Exit resets the throttle — treat as if no prior attachment exists
|
||||
break
|
||||
@@ -1322,10 +1322,10 @@ function countAutoModeAttachmentsSinceLastExit(messages: Message[]): number {
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const message = messages[i]
|
||||
if (message?.type === 'attachment') {
|
||||
if (message.attachment.type === 'auto_mode_exit') {
|
||||
if (message.attachment!.type === 'auto_mode_exit') {
|
||||
break
|
||||
}
|
||||
if (message.attachment.type === 'auto_mode') {
|
||||
if (message.attachment!.type === 'auto_mode') {
|
||||
count++
|
||||
}
|
||||
}
|
||||
@@ -1525,9 +1525,9 @@ export function getAgentListingDeltaAttachment(
|
||||
const announced = new Set<string>()
|
||||
for (const msg of messages ?? []) {
|
||||
if (msg.type !== 'attachment') continue
|
||||
if (msg.attachment.type !== 'agent_listing_delta') continue
|
||||
for (const t of msg.attachment.addedTypes as string[]) announced.add(t)
|
||||
for (const t of msg.attachment.removedTypes as string[]) announced.delete(t)
|
||||
if (msg.attachment!.type !== 'agent_listing_delta') continue
|
||||
for (const t of msg.attachment!.addedTypes as string[]) announced.add(t)
|
||||
for (const t of msg.attachment!.removedTypes as string[]) announced.delete(t)
|
||||
}
|
||||
|
||||
const currentTypes = new Set(filtered.map(a => a.agentType))
|
||||
@@ -2256,8 +2256,8 @@ export function collectSurfacedMemories(messages: ReadonlyArray<Message>): {
|
||||
const paths = new Set<string>()
|
||||
let totalBytes = 0
|
||||
for (const m of messages) {
|
||||
if (m.type === 'attachment' && m.attachment.type === 'relevant_memories') {
|
||||
for (const mem of m.attachment.memories as { path: string; content: string; mtimeMs: number }[]) {
|
||||
if (m.type === 'attachment' && m.attachment!.type === 'relevant_memories') {
|
||||
for (const mem of m.attachment!.memories as { path: string; content: string; mtimeMs: number }[]) {
|
||||
paths.add(mem.path)
|
||||
totalBytes += mem.content.length
|
||||
}
|
||||
@@ -2473,16 +2473,16 @@ export function collectRecentSuccessfulTools(
|
||||
const m = messages[i]
|
||||
if (!m) continue
|
||||
if (isHumanTurn(m) && m !== lastUserMessage) break
|
||||
if (m.type === 'assistant' && typeof m.message.content !== 'string') {
|
||||
for (const block of m.message.content) {
|
||||
if (m.type === 'assistant' && typeof m.message!.content !== 'string') {
|
||||
for (const block of m.message!.content as Array<{type: string; id: string; name: string}>) {
|
||||
if (block.type === 'tool_use') useIdToName.set(block.id, block.name)
|
||||
}
|
||||
} else if (
|
||||
m.type === 'user' &&
|
||||
'message' in m &&
|
||||
Array.isArray(m.message.content)
|
||||
Array.isArray(m.message!.content)
|
||||
) {
|
||||
for (const block of m.message.content) {
|
||||
for (const block of m.message!.content as Array<{type: string}>) {
|
||||
if (isToolResultBlock(block)) {
|
||||
resultByUseId.set(block.tool_use_id, block.is_error === true)
|
||||
}
|
||||
@@ -3201,13 +3201,13 @@ export async function generateFileAttachment(
|
||||
|
||||
export function createAttachmentMessage(
|
||||
attachment: Attachment,
|
||||
): AttachmentMessage {
|
||||
): AttachmentMessage<Attachment> {
|
||||
return {
|
||||
attachment,
|
||||
type: 'attachment',
|
||||
uuid: randomUUID(),
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
} as unknown as AttachmentMessage<Attachment>
|
||||
}
|
||||
|
||||
function getTodoReminderTurnCounts(messages: Message[]): {
|
||||
@@ -3248,7 +3248,7 @@ function getTodoReminderTurnCounts(messages: Message[]): {
|
||||
} else if (
|
||||
lastReminderIndex === -1 &&
|
||||
message?.type === 'attachment' &&
|
||||
message.attachment.type === 'todo_reminder'
|
||||
message.attachment!.type === 'todo_reminder'
|
||||
) {
|
||||
lastReminderIndex = i
|
||||
}
|
||||
@@ -3357,7 +3357,7 @@ function getTaskReminderTurnCounts(messages: Message[]): {
|
||||
} else if (
|
||||
lastReminderIndex === -1 &&
|
||||
message?.type === 'attachment' &&
|
||||
message.attachment.type === 'task_reminder'
|
||||
message.attachment!.type === 'task_reminder'
|
||||
) {
|
||||
lastReminderIndex = i
|
||||
}
|
||||
@@ -3880,7 +3880,7 @@ export function getVerifyPlanReminderTurnCount(messages: Message[]): number {
|
||||
// Stop counting at plan_mode_exit attachment (marks when implementation started)
|
||||
if (
|
||||
message?.type === 'attachment' &&
|
||||
message.attachment.type === 'plan_mode_exit'
|
||||
message.attachment!.type === 'plan_mode_exit'
|
||||
) {
|
||||
return turnCount
|
||||
}
|
||||
|
||||
@@ -940,7 +940,7 @@ export function collapseReadSearchGroups(
|
||||
// suppresses the fallback). createCollapsedGroup adds .length to
|
||||
// memoryReadCount after the readCount subtraction instead.
|
||||
currentGroup.relevantMemories ??= []
|
||||
currentGroup.relevantMemories.push(...msg.attachment.memories)
|
||||
currentGroup.relevantMemories.push(...(msg.attachment.memories ?? []))
|
||||
} else if (shouldSkipMessage(msg)) {
|
||||
// Don't flush the group for skippable messages (thinking, attachments, system)
|
||||
// If a group is in progress, defer these messages to output after the collapsed group
|
||||
|
||||
@@ -43,7 +43,7 @@ export function collapseTeammateShutdowns(
|
||||
type: 'teammate_shutdown_batch',
|
||||
count,
|
||||
},
|
||||
})
|
||||
} as unknown as RenderableMessage)
|
||||
}
|
||||
} else {
|
||||
result.push(msg)
|
||||
|
||||
@@ -394,10 +394,10 @@ public class WScroll {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const screenshot: ScreenshotPlatform = {
|
||||
async captureScreen(displayId) {
|
||||
async captureScreen(displayId): Promise<ScreenshotResult> {
|
||||
// If HWND is bound, capture that specific window
|
||||
if (boundHwnd) {
|
||||
const result = this.captureWindow?.(String(boundHwnd))
|
||||
const result = await this.captureWindow?.(String(boundHwnd))
|
||||
if (result) return result
|
||||
}
|
||||
|
||||
@@ -415,10 +415,10 @@ const screenshot: ScreenshotPlatform = {
|
||||
)
|
||||
},
|
||||
|
||||
async captureRegion(x, y, w, h) {
|
||||
async captureRegion(x, y, w, h): Promise<ScreenshotResult> {
|
||||
// When HWND is bound, the window IS the region (matches macOS behavior)
|
||||
if (boundHwnd) {
|
||||
const result = this.captureWindow?.(String(boundHwnd))
|
||||
const result = await this.captureWindow?.(String(boundHwnd))
|
||||
if (result) return result
|
||||
}
|
||||
return this.captureScreen()
|
||||
|
||||
@@ -46,14 +46,14 @@ export function analyzeContext(messages: Message[]): TokenStats {
|
||||
|
||||
messages.forEach(msg => {
|
||||
if (msg.type === 'attachment') {
|
||||
const type = msg.attachment.type || 'unknown'
|
||||
const type = msg.attachment!.type || 'unknown'
|
||||
stats.attachments.set(type, (stats.attachments.get(type) || 0) + 1)
|
||||
}
|
||||
})
|
||||
|
||||
const normalizedMessages = normalizeMessagesForAPI(messages)
|
||||
normalizedMessages.forEach(msg => {
|
||||
const { content } = msg.message
|
||||
const { content } = msg.message!
|
||||
|
||||
// Not sure if this path is still used, but adding as a fallback
|
||||
if (typeof content === 'string') {
|
||||
@@ -67,7 +67,7 @@ export function analyzeContext(messages: Message[]): TokenStats {
|
||||
tokens
|
||||
}
|
||||
} else {
|
||||
content.forEach(block =>
|
||||
content!.forEach(block =>
|
||||
processBlock(
|
||||
block,
|
||||
msg,
|
||||
|
||||
@@ -124,7 +124,7 @@ function migrateLegacyAttachmentTypes(message: Message): Message {
|
||||
...attachment,
|
||||
displayPath: relative(getCwd(), path),
|
||||
},
|
||||
} as Message
|
||||
} as unknown as Message
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,7 +359,7 @@ function isTerminalToolResult(
|
||||
for (let i = resultIdx - 1; i >= 0; i--) {
|
||||
const msg = messages[i]!
|
||||
if (msg.type !== 'assistant') continue
|
||||
const msgContent = msg.message.content
|
||||
const msgContent = msg.message!.content
|
||||
if (!Array.isArray(msgContent)) continue
|
||||
for (const b of msgContent) {
|
||||
if (typeof b !== 'string' && 'type' in b && b.type === 'tool_use' && 'id' in b && b.id === toolUseId) {
|
||||
@@ -386,8 +386,8 @@ export function restoreSkillStateFromMessages(messages: Message[]): void {
|
||||
if (message.type !== 'attachment') {
|
||||
continue
|
||||
}
|
||||
if (message.attachment.type === 'invoked_skills') {
|
||||
const skills = message.attachment.skills as Array<{ name?: string; path?: string; content?: string }>;
|
||||
if (message.attachment!.type === 'invoked_skills') {
|
||||
const skills = message.attachment!.skills as Array<{ name?: string; path?: string; content?: string }>;
|
||||
for (const skill of skills) {
|
||||
if (skill.name && skill.path && skill.content) {
|
||||
// Resume only happens for the main session, so agentId is null
|
||||
@@ -399,7 +399,7 @@ export function restoreSkillStateFromMessages(messages: Message[]): void {
|
||||
// in the transcript the model is about to see. sentSkillNames is
|
||||
// process-local, so without this every resume re-announces the same
|
||||
// ~600 tokens. Fire-once latch; consumed on the first attachment pass.
|
||||
if (message.attachment.type === 'skill_listing') {
|
||||
if (message.attachment!.type === 'skill_listing') {
|
||||
suppressNextSkillListing()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ function StaticKeybindingProvider({
|
||||
// AttachmentMessage etc. have no .message and normalize to ≤1.
|
||||
function normalizedUpperBound(m: Message): number {
|
||||
if (!('message' in m)) return 1
|
||||
const c = m.message.content
|
||||
const c = m.message!.content
|
||||
return Array.isArray(c) ? c.length : 1
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ export function applyGrouping(
|
||||
const results: NormalizedUserMessage[] = []
|
||||
for (const assistantMsg of group) {
|
||||
const toolUseId = (
|
||||
assistantMsg.message.content[0] as { id: string }
|
||||
assistantMsg.message!.content![0] as { id: string }
|
||||
).id
|
||||
const resultMsg = resultsByToolUseId.get(toolUseId)
|
||||
if (resultMsg) {
|
||||
|
||||
@@ -2377,7 +2377,7 @@ async function* executeHooks({
|
||||
)
|
||||
// Inject timing fields for hook visibility
|
||||
if (promptResult.message?.type === 'attachment') {
|
||||
const att = promptResult.message.attachment
|
||||
const att = promptResult.message.attachment!
|
||||
if (
|
||||
att.type === 'hook_success' ||
|
||||
att.type === 'hook_non_blocking_error'
|
||||
@@ -2417,7 +2417,7 @@ async function* executeHooks({
|
||||
)
|
||||
// Inject timing fields for hook visibility
|
||||
if (agentResult.message?.type === 'attachment') {
|
||||
const att = agentResult.message.attachment
|
||||
const att = agentResult.message.attachment!
|
||||
if (
|
||||
att.type === 'hook_success' ||
|
||||
att.type === 'hook_non_blocking_error'
|
||||
|
||||
@@ -117,7 +117,7 @@ export function createApiQueryHook<TResult>(
|
||||
type: 'success',
|
||||
queryName: config.name,
|
||||
result,
|
||||
messageId: response.message.id,
|
||||
messageId: response.message.id ?? '',
|
||||
model,
|
||||
uuid,
|
||||
},
|
||||
|
||||
@@ -327,7 +327,7 @@ export function groupHooksByEventAndMatcher(
|
||||
const eventGroup = grouped[hookEvent]
|
||||
if (!eventGroup) continue
|
||||
|
||||
for (const matcher of matchers) {
|
||||
for (const matcher of (matchers ?? [])) {
|
||||
const matcherKey = matcher.matcher || ''
|
||||
|
||||
// Only PluginHookMatcher has pluginRoot; HookCallbackMatcher (internal
|
||||
|
||||
@@ -41,10 +41,10 @@ function formatRecentMessages(messages: Message[]): string {
|
||||
.filter(m => m.type === 'user' || m.type === 'assistant')
|
||||
.map(m => {
|
||||
const role = m.type === 'user' ? 'User' : 'Assistant'
|
||||
const content = m.message.content
|
||||
const content = m.message!.content
|
||||
if (typeof content === 'string')
|
||||
return `${role}: ${content.slice(0, 500)}`
|
||||
const text = content
|
||||
const text = (content ?? [])
|
||||
.filter(
|
||||
(b): b is Extract<typeof b, { type: 'text' }> => b.type === 'text',
|
||||
)
|
||||
|
||||
@@ -63,10 +63,10 @@ export function getMcpInstructionsDelta(
|
||||
for (const msg of messages) {
|
||||
if (msg.type !== 'attachment') continue
|
||||
attachmentCount++
|
||||
if (msg.attachment.type !== 'mcp_instructions_delta') continue
|
||||
if (msg.attachment!.type !== 'mcp_instructions_delta') continue
|
||||
midCount++
|
||||
for (const n of (msg.attachment as any).addedNames) announced.add(n)
|
||||
for (const n of (msg.attachment as any).removedNames) announced.delete(n)
|
||||
for (const n of (msg.attachment! as any).addedNames) announced.add(n)
|
||||
for (const n of (msg.attachment! as any).removedNames) announced.delete(n)
|
||||
}
|
||||
|
||||
const connected = mcpClients.filter(
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -48,10 +48,9 @@ export function toInternalMessages(
|
||||
uuid: message.uuid ?? randomUUID(),
|
||||
timestamp: message.timestamp ?? new Date().toISOString(),
|
||||
isMeta: message.isSynthetic,
|
||||
} as Message,
|
||||
} as unknown as Message,
|
||||
]
|
||||
case 'system':
|
||||
// Handle compact boundary messages
|
||||
// Handle compact boundary messages
|
||||
if (message.subtype === 'compact_boundary') {
|
||||
const compactMsg = message
|
||||
return [
|
||||
@@ -272,7 +271,7 @@ function normalizeAssistantMessageForSDK(
|
||||
|
||||
const normalizedContent = content.map((block): BetaContentBlock => {
|
||||
if (block.type !== 'tool_use') {
|
||||
return block
|
||||
return block as unknown as BetaContentBlock
|
||||
}
|
||||
|
||||
if (block.name === EXIT_PLAN_MODE_V2_TOOL_NAME) {
|
||||
|
||||
@@ -171,13 +171,13 @@ export async function readNotebook(
|
||||
const notebook = jsonParse(content) as NotebookContent
|
||||
const language = notebook.metadata.language_info?.name ?? 'python'
|
||||
if (cellId) {
|
||||
const cell = notebook.cells.find(c => c.id === cellId)
|
||||
const cell = notebook.cells.find((c: NotebookCell) => c.id === cellId)
|
||||
if (!cell) {
|
||||
throw new Error(`Cell with ID "${cellId}" not found in notebook`)
|
||||
}
|
||||
return [processCell(cell, notebook.cells.indexOf(cell), language, true)]
|
||||
}
|
||||
return notebook.cells.map((cell, index) =>
|
||||
return notebook.cells.map((cell: NotebookCell, index: number) =>
|
||||
processCell(cell, index, language, false),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -302,8 +302,8 @@ export type TranscriptEntry = {
|
||||
export function buildTranscriptEntries(messages: Message[]): TranscriptEntry[] {
|
||||
const transcript: TranscriptEntry[] = []
|
||||
for (const msg of messages) {
|
||||
if (msg.type === 'attachment' && msg.attachment.type === 'queued_command') {
|
||||
const prompt = msg.attachment.prompt
|
||||
if (msg.type === 'attachment' && msg.attachment!.type === 'queued_command') {
|
||||
const prompt = msg.attachment!.prompt
|
||||
let text: string | null = null
|
||||
if (typeof prompt === 'string') {
|
||||
text = prompt
|
||||
@@ -324,7 +324,7 @@ export function buildTranscriptEntries(messages: Message[]): TranscriptEntry[] {
|
||||
})
|
||||
}
|
||||
} else if (msg.type === 'user') {
|
||||
const content = msg.message.content
|
||||
const content = msg.message!.content
|
||||
const textBlocks: TranscriptBlock[] = []
|
||||
if (typeof content === 'string') {
|
||||
textBlocks.push({ type: 'text', text: content })
|
||||
@@ -340,7 +340,7 @@ export function buildTranscriptEntries(messages: Message[]): TranscriptEntry[] {
|
||||
}
|
||||
} else if (msg.type === 'assistant') {
|
||||
const blocks: TranscriptBlock[] = []
|
||||
for (const block of msg.message.content) {
|
||||
for (const block of (msg.message!.content ?? [])) {
|
||||
// Only include tool_use blocks — assistant text is model-authored
|
||||
// and could be crafted to influence the classifier's decision.
|
||||
if (typeof block !== 'string' && block.type === 'tool_use') {
|
||||
|
||||
@@ -69,7 +69,7 @@ function convertPluginHooksToMatchers(
|
||||
continue
|
||||
}
|
||||
|
||||
for (const matcher of matchers) {
|
||||
for (const matcher of (matchers ?? [])) {
|
||||
if (matcher.hooks.length > 0) {
|
||||
pluginMatchers[hookEvent].push({
|
||||
matcher: matcher.matcher,
|
||||
@@ -195,7 +195,7 @@ export async function pruneRemovedPluginHooks(): Promise<void> {
|
||||
// clearRegisteredPluginHooks; we only need to re-register survivors.
|
||||
const survivors: Partial<Record<HookEvent, PluginHookMatcher[]>> = {}
|
||||
for (const [event, matchers] of Object.entries(current)) {
|
||||
const kept = matchers.filter(
|
||||
const kept = (matchers ?? []).filter(
|
||||
(m): m is PluginHookMatcher =>
|
||||
'pluginRoot' in m && enabledRoots.has(m.pluginRoot),
|
||||
)
|
||||
|
||||
@@ -259,7 +259,7 @@ export function resolvePluginLspEnvironment(
|
||||
|
||||
// Resolve args
|
||||
if (resolved.args) {
|
||||
resolved.args = resolved.args.map(arg => resolveValue(arg))
|
||||
resolved.args = resolved.args.map((arg: string) => resolveValue(arg))
|
||||
}
|
||||
|
||||
// Resolve environment variables and add CLAUDE_PLUGIN_ROOT / CLAUDE_PLUGIN_DATA
|
||||
|
||||
@@ -81,6 +81,7 @@ import {
|
||||
setPluginSettingsBase,
|
||||
} from '../settings/settingsCache.js'
|
||||
import type { HooksSettings } from '../settings/types.js'
|
||||
import type { HookMatcher } from '../../schemas/hooks.js'
|
||||
import { SettingsSchema } from '../settings/types.js'
|
||||
import { jsonParse, jsonStringify } from '../slowOperations.js'
|
||||
import { getAddDirEnabledPlugins } from './addDirPluginSettings.js'
|
||||
@@ -1861,15 +1862,13 @@ function mergeHooksSettings(
|
||||
|
||||
const merged = { ...base }
|
||||
|
||||
for (const [event, matchers] of Object.entries(additional)) {
|
||||
for (const [event, matchers] of Object.entries(additional) as [string, HookMatcher[]][]) {
|
||||
if (!merged[event as keyof HooksSettings]) {
|
||||
merged[event as keyof HooksSettings] = matchers
|
||||
} else {
|
||||
// Merge matchers for this event
|
||||
merged[event as keyof HooksSettings] = [
|
||||
...(merged[event as keyof HooksSettings] || []),
|
||||
...matchers,
|
||||
]
|
||||
const existing = ((merged[event as keyof HooksSettings] as unknown) ?? []) as HookMatcher[]
|
||||
merged[event as keyof HooksSettings] = [...existing, ...matchers]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -241,17 +241,17 @@ export async function processUserInput({
|
||||
|
||||
// TODO: Clean this up
|
||||
if (hookResult.message) {
|
||||
switch (hookResult.message.attachment.type) {
|
||||
switch (hookResult.message.attachment!.type) {
|
||||
case 'hook_success':
|
||||
if (!hookResult.message.attachment.content) {
|
||||
if (!hookResult.message.attachment!.content) {
|
||||
// Skip if there is no content
|
||||
break
|
||||
}
|
||||
result.messages.push({
|
||||
...hookResult.message,
|
||||
attachment: {
|
||||
...hookResult.message.attachment,
|
||||
content: applyTruncation(hookResult.message.attachment.content as string),
|
||||
...hookResult.message.attachment!,
|
||||
content: applyTruncation(hookResult.message.attachment!.content as string),
|
||||
},
|
||||
} as AttachmentMessage)
|
||||
break
|
||||
|
||||
@@ -135,7 +135,7 @@ export async function buildSideQuestionFallbackParams({
|
||||
// as btw.tsx. The SDK can fire side_question mid-turn.
|
||||
const last = messages.at(-1)
|
||||
const forkContextMessages =
|
||||
last?.type === 'assistant' && last.message.stop_reason === null
|
||||
last?.type === 'assistant' && last.message!.stop_reason === null
|
||||
? messages.slice(0, -1)
|
||||
: messages
|
||||
|
||||
|
||||
@@ -68,7 +68,8 @@ export function isResultSuccessful(
|
||||
if (!message) return false
|
||||
|
||||
if (message.type === 'assistant') {
|
||||
const lastContent = last(message.message.content)
|
||||
const content = message.message!.content
|
||||
const lastContent = Array.isArray(content) ? content[content.length - 1] : undefined
|
||||
return (
|
||||
lastContent?.type === 'text' ||
|
||||
lastContent?.type === 'thinking' ||
|
||||
@@ -78,7 +79,7 @@ export function isResultSuccessful(
|
||||
|
||||
if (message.type === 'user') {
|
||||
// Check if all content blocks are tool_result type
|
||||
const content = message.message.content
|
||||
const content = message.message!.content
|
||||
if (
|
||||
Array.isArray(content) &&
|
||||
content.length > 0 &&
|
||||
@@ -323,8 +324,8 @@ export async function* handleOrphanedPermission(
|
||||
const alreadyPresent = mutableMessages.some(
|
||||
m =>
|
||||
m.type === 'assistant' &&
|
||||
Array.isArray(m.message.content) &&
|
||||
m.message.content.some(
|
||||
Array.isArray(m.message!.content) &&
|
||||
m.message!.content.some(
|
||||
b => b.type === 'tool_use' && 'id' in b && b.id === toolUseID,
|
||||
),
|
||||
)
|
||||
@@ -385,9 +386,9 @@ export function extractReadFilesFromMessages(
|
||||
for (const message of messages) {
|
||||
if (
|
||||
message.type === 'assistant' &&
|
||||
Array.isArray(message.message.content)
|
||||
Array.isArray(message.message!.content)
|
||||
) {
|
||||
for (const content of message.message.content) {
|
||||
for (const content of message.message!.content) {
|
||||
if (
|
||||
content.type === 'tool_use' &&
|
||||
content.name === FILE_READ_TOOL_NAME
|
||||
@@ -442,8 +443,8 @@ export function extractReadFilesFromMessages(
|
||||
|
||||
// Second pass: find corresponding tool results and extract content
|
||||
for (const message of messages) {
|
||||
if (message.type === 'user' && Array.isArray(message.message.content)) {
|
||||
for (const content of message.message.content) {
|
||||
if (message.type === 'user' && Array.isArray(message.message!.content)) {
|
||||
for (const content of message.message!.content) {
|
||||
if (content.type === 'tool_result' && content.tool_use_id) {
|
||||
// Handle Read tool results
|
||||
const readFilePath = fileReadToolUseIds.get(content.tool_use_id)
|
||||
@@ -537,9 +538,9 @@ export function extractBashToolsFromMessages(messages: Message[]): Set<string> {
|
||||
for (const message of messages) {
|
||||
if (
|
||||
message.type === 'assistant' &&
|
||||
Array.isArray(message.message.content)
|
||||
Array.isArray(message.message!.content)
|
||||
) {
|
||||
for (const content of message.message.content) {
|
||||
for (const content of message.message!.content) {
|
||||
if (content.type === 'tool_use' && content.name === BASH_TOOL_NAME) {
|
||||
const { input } = content
|
||||
if (
|
||||
|
||||
@@ -78,7 +78,7 @@ function extractTodosFromTranscript(messages: Message[]): TodoList {
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const msg = messages[i]
|
||||
if (msg?.type !== 'assistant') continue
|
||||
const toolUse = (msg.message.content as any[]).find(
|
||||
const toolUse = (msg.message!.content as any[]).find(
|
||||
block => block.type === 'tool_use' && block.name === TODO_WRITE_TOOL_NAME,
|
||||
)
|
||||
if (!toolUse || toolUse.type !== 'tool_use') continue
|
||||
|
||||
@@ -1927,9 +1927,9 @@ function applyPreservedSegmentRelinks(
|
||||
messages.set(uuid, {
|
||||
...msg,
|
||||
message: {
|
||||
...msg.message,
|
||||
...msg.message!,
|
||||
usage: {
|
||||
...msg.message.usage,
|
||||
...msg.message!.usage,
|
||||
input_tokens: 0,
|
||||
output_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
@@ -2131,7 +2131,7 @@ function recoverOrphanedParallelToolResults(
|
||||
// already in chain order, so later iterations overwrite → last wins.
|
||||
const anchorByMsgId = new Map<string, ChainAssistant>()
|
||||
for (const a of chainAssistants) {
|
||||
if (a.message.id) anchorByMsgId.set(a.message.id, a)
|
||||
if (a.message!.id) anchorByMsgId.set(a.message!.id, a)
|
||||
}
|
||||
|
||||
// O(n) precompute: sibling groups and TR index.
|
||||
@@ -2140,15 +2140,15 @@ function recoverOrphanedParallelToolResults(
|
||||
const siblingsByMsgId = new Map<string, TranscriptMessage[]>()
|
||||
const toolResultsByAsst = new Map<UUID, TranscriptMessage[]>()
|
||||
for (const m of messages.values()) {
|
||||
if (m.type === 'assistant' && m.message.id) {
|
||||
const group = siblingsByMsgId.get(m.message.id)
|
||||
if (m.type === 'assistant' && m.message!.id) {
|
||||
const group = siblingsByMsgId.get(m.message!.id)
|
||||
if (group) group.push(m)
|
||||
else siblingsByMsgId.set(m.message.id, [m])
|
||||
else siblingsByMsgId.set(m.message!.id, [m])
|
||||
} else if (
|
||||
m.type === 'user' &&
|
||||
m.parentUuid &&
|
||||
Array.isArray(m.message.content) &&
|
||||
m.message.content.some(b => b.type === 'tool_result')
|
||||
Array.isArray(m.message!.content) &&
|
||||
(m.message!.content as Array<{type: string}>).some(b => b.type === 'tool_result')
|
||||
) {
|
||||
const group = toolResultsByAsst.get(m.parentUuid)
|
||||
if (group) group.push(m)
|
||||
@@ -2164,7 +2164,7 @@ function recoverOrphanedParallelToolResults(
|
||||
const inserts = new Map<UUID, TranscriptMessage[]>()
|
||||
let recoveredCount = 0
|
||||
for (const asst of chainAssistants) {
|
||||
const msgId = asst.message.id
|
||||
const msgId = asst.message!.id
|
||||
if (!msgId || processedGroups.has(msgId)) continue
|
||||
processedGroups.add(msgId)
|
||||
|
||||
@@ -4357,7 +4357,7 @@ export function isLoggableMessage(m: Message): boolean {
|
||||
// user-configured hook output that is useful for session context on resume.
|
||||
if (m.type === 'attachment' && getUserType() !== 'ant') {
|
||||
if (
|
||||
m.attachment.type === 'hook_additional_context' &&
|
||||
m.attachment!.type === 'hook_additional_context' &&
|
||||
isEnvTruthy(process.env.CLAUDE_CODE_SAVE_HOOK_ADDITIONAL_CONTEXT)
|
||||
) {
|
||||
return true
|
||||
@@ -4370,8 +4370,8 @@ export function isLoggableMessage(m: Message): boolean {
|
||||
function collectReplIds(messages: readonly Message[]): Set<string> {
|
||||
const ids = new Set<string>()
|
||||
for (const m of messages) {
|
||||
if (m.type === 'assistant' && Array.isArray(m.message.content)) {
|
||||
for (const b of m.message.content) {
|
||||
if (m.type === 'assistant' && Array.isArray(m.message!.content)) {
|
||||
for (const b of m.message!.content as Array<{type: string; name: string; id: string}>) {
|
||||
if (b.type === 'tool_use' && b.name === REPL_TOOL_NAME) {
|
||||
ids.add(b.id)
|
||||
}
|
||||
@@ -4488,9 +4488,9 @@ export async function findUnresolvedToolUse(
|
||||
// Find the tool use but make sure there's not also a result
|
||||
for (const message of messages.values()) {
|
||||
if (message.type === 'assistant') {
|
||||
const content = message.message.content
|
||||
const content = message.message!.content
|
||||
if (Array.isArray(content)) {
|
||||
for (const block of content) {
|
||||
for (const block of content as Array<{type: string; id: string}>) {
|
||||
if (block.type === 'tool_use' && block.id === toolUseId) {
|
||||
toolUseMessage = message
|
||||
break
|
||||
@@ -4498,9 +4498,9 @@ export async function findUnresolvedToolUse(
|
||||
}
|
||||
}
|
||||
} else if (message.type === 'user') {
|
||||
const content = message.message.content
|
||||
const content = message.message!.content
|
||||
if (Array.isArray(content)) {
|
||||
for (const block of content) {
|
||||
for (const block of content as Array<{type: string; tool_use_id: string}>) {
|
||||
if (
|
||||
block.type === 'tool_result' &&
|
||||
block.tool_use_id === toolUseId
|
||||
@@ -4513,7 +4513,7 @@ export async function findUnresolvedToolUse(
|
||||
}
|
||||
}
|
||||
|
||||
return toolUseMessage
|
||||
return toolUseMessage as AssistantMessage | null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export function extractConversationText(messages: Message[]): string {
|
||||
if (msg.type !== 'user' && msg.type !== 'assistant') continue
|
||||
if ('isMeta' in msg && msg.isMeta) continue
|
||||
if ('origin' in msg && (msg as any).origin && (msg as any).origin.kind !== 'human') continue
|
||||
const content = msg.message.content
|
||||
const content = msg.message!.content
|
||||
if (typeof content === 'string') {
|
||||
parts.push(content)
|
||||
} else if (Array.isArray(content)) {
|
||||
|
||||
@@ -125,7 +125,7 @@ ${question}`
|
||||
function extractSideQuestionResponse(messages: Message[]): string | null {
|
||||
// Flatten all assistant content blocks across the per-block messages.
|
||||
const assistantBlocks = messages.flatMap(m =>
|
||||
m.type === 'assistant' ? (m.message.content as unknown as Array<{ type: string; [key: string]: unknown }>) : [],
|
||||
m.type === 'assistant' ? (m.message!.content as unknown as Array<{ type: string; [key: string]: unknown }>) : [],
|
||||
)
|
||||
|
||||
if (assistantBlocks.length > 0) {
|
||||
|
||||
@@ -110,7 +110,7 @@ function accumulateToolUses(
|
||||
message: SDKAssistantMessage,
|
||||
counts: ToolCounts,
|
||||
): void {
|
||||
const content = message.message.content
|
||||
const content = message.message!.content
|
||||
if (!Array.isArray(content)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1235,7 +1235,7 @@ export async function runInProcessTeammate(
|
||||
// Track in-progress tool use IDs for animation in transcript view
|
||||
let inProgressToolUseIDs = task.inProgressToolUseIDs
|
||||
if (message.type === 'assistant') {
|
||||
for (const block of (Array.isArray(message.message.content) ? message.message.content : [])) {
|
||||
for (const block of (Array.isArray(message.message!.content) ? message.message!.content : [])) {
|
||||
if (typeof block !== 'string' && block.type === 'tool_use') {
|
||||
inProgressToolUseIDs = new Set([
|
||||
...(inProgressToolUseIDs ?? []),
|
||||
@@ -1244,7 +1244,7 @@ export async function runInProcessTeammate(
|
||||
}
|
||||
}
|
||||
} else if (message.type === 'user') {
|
||||
const content = message.message.content
|
||||
const content = message.message!.content
|
||||
if (Array.isArray(content)) {
|
||||
for (const block of content) {
|
||||
if (
|
||||
|
||||
@@ -1152,7 +1152,7 @@ export function getLastPeerDmSummary(messages: Message[]): string | undefined {
|
||||
if (!msg) continue
|
||||
|
||||
// Stop at wake-up boundary: a user prompt (string content), not tool results (array content)
|
||||
if (msg.type === 'user' && typeof msg.message.content === 'string') {
|
||||
if (msg.type === 'user' && typeof msg.message!.content === 'string') {
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ async function generateTitleAndBranch(
|
||||
})
|
||||
|
||||
// Extract text from the response
|
||||
const firstBlock = response.message.content[0] as { type?: string; text?: string } | undefined
|
||||
const firstBlock = response.message!.content?.[0] as { type?: string; text?: string } | undefined
|
||||
if (firstBlock?.type !== 'text') {
|
||||
return { title: fallbackTitle, branchName: fallbackBranch }
|
||||
}
|
||||
|
||||
@@ -30,10 +30,10 @@ export function getTokenUsage(message: Message): Usage | undefined {
|
||||
function getAssistantMessageId(message: Message): string | undefined {
|
||||
if (
|
||||
message?.type === 'assistant' &&
|
||||
'id' in message.message &&
|
||||
message.message.model !== SYNTHETIC_MODEL
|
||||
'id' in message.message! &&
|
||||
message.message!.model !== SYNTHETIC_MODEL
|
||||
) {
|
||||
return message.message.id
|
||||
return message.message!.id
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -537,7 +537,7 @@ function buildToolNameMap(messages: Message[]): Map<string, string> {
|
||||
const map = new Map<string, string>()
|
||||
for (const message of messages) {
|
||||
if (message.type !== 'assistant') continue
|
||||
const content = message.message.content
|
||||
const content = message.message!.content
|
||||
if (!Array.isArray(content)) continue
|
||||
for (const block of content) {
|
||||
if (block.type === 'tool_use') {
|
||||
@@ -555,10 +555,10 @@ function buildToolNameMap(messages: Message[]): Map<string, string> {
|
||||
* Returns [] for messages with no eligible blocks.
|
||||
*/
|
||||
function collectCandidatesFromMessage(message: Message): ToolResultCandidate[] {
|
||||
if (message.type !== 'user' || !Array.isArray(message.message.content)) {
|
||||
if (message.type !== 'user' || !Array.isArray(message.message!.content)) {
|
||||
return []
|
||||
}
|
||||
return message.message.content.flatMap(block => {
|
||||
return message.message!.content.flatMap(block => {
|
||||
if (block.type !== 'tool_result' || !block.content) return []
|
||||
if (isContentAlreadyCompacted(block.content)) return []
|
||||
if (hasImageBlock(block.content)) return []
|
||||
@@ -625,9 +625,9 @@ function collectCandidatesByMessage(
|
||||
if (message.type === 'user') {
|
||||
current.push(...collectCandidatesFromMessage(message))
|
||||
} else if (message.type === 'assistant') {
|
||||
if (!seenAsstIds.has(message.message.id)) {
|
||||
if (!seenAsstIds.has(message.message!.id ?? '')) {
|
||||
flush()
|
||||
seenAsstIds.add(message.message.id)
|
||||
seenAsstIds.add(message.message!.id ?? '')
|
||||
}
|
||||
}
|
||||
// progress / attachment / system are filtered or merged by
|
||||
@@ -701,10 +701,10 @@ function replaceToolResultContents(
|
||||
replacementMap: Map<string, string>,
|
||||
): Message[] {
|
||||
return messages.map(message => {
|
||||
if (message.type !== 'user' || !Array.isArray(message.message.content)) {
|
||||
if (message.type !== 'user' || !Array.isArray(message.message!.content)) {
|
||||
return message
|
||||
}
|
||||
const content = message.message.content
|
||||
const content = message.message!.content
|
||||
const needsReplace = content.some(
|
||||
b => b.type === 'tool_result' && replacementMap.has(b.tool_use_id),
|
||||
)
|
||||
|
||||
@@ -655,11 +655,11 @@ export function getDeferredToolsDelta(
|
||||
for (const msg of messages) {
|
||||
if (msg.type !== 'attachment') continue
|
||||
attachmentCount++
|
||||
attachmentTypesSeen.add(msg.attachment.type)
|
||||
if (msg.attachment.type !== 'deferred_tools_delta') continue
|
||||
attachmentTypesSeen.add(msg.attachment!.type)
|
||||
if (msg.attachment!.type !== 'deferred_tools_delta') continue
|
||||
dtdCount++
|
||||
for (const n of (msg.attachment as any).addedNames) announced.add(n)
|
||||
for (const n of (msg.attachment as any).removedNames) announced.delete(n)
|
||||
for (const n of msg.attachment!.addedNames) announced.add(n)
|
||||
for (const n of msg.attachment!.removedNames) announced.delete(n)
|
||||
}
|
||||
|
||||
const deferred: Tool[] = tools.filter(isDeferredTool)
|
||||
|
||||
@@ -33,12 +33,12 @@ function computeSearchText(msg: RenderableMessage): string {
|
||||
let raw = ''
|
||||
switch (msg.type) {
|
||||
case 'user': {
|
||||
const c = msg.message.content
|
||||
const c = msg.message!.content
|
||||
if (typeof c === 'string') {
|
||||
raw = RENDERED_AS_SENTINEL.has(c) ? '' : c
|
||||
} else {
|
||||
const parts: string[] = []
|
||||
for (const b of c) {
|
||||
for (const b of (c ?? [])) {
|
||||
if (b.type === 'text') {
|
||||
if (!RENDERED_AS_SENTINEL.has(b.text)) parts.push(b.text)
|
||||
} else if (b.type === 'tool_result') {
|
||||
@@ -83,8 +83,8 @@ function computeSearchText(msg: RenderableMessage): string {
|
||||
// relevant_memories renders full m.content in transcript mode
|
||||
// (AttachmentMessage.tsx <Ansi>{m.content}</Ansi>). Visible but
|
||||
// unsearchable without this — [ dump finds it, / doesn't.
|
||||
if (msg.attachment.type === 'relevant_memories') {
|
||||
raw = msg.attachment.memories.map(m => m.content).join('\n')
|
||||
if (msg.attachment!.type === 'relevant_memories') {
|
||||
raw = (msg.attachment!.memories ?? []).map((m: { content: string }) => m.content).join('\n')
|
||||
} else if (
|
||||
// Mid-turn prompts — queued while an agent is running. Render via
|
||||
// UserTextMessage (AttachmentMessage.tsx:~348). stickyPromptText
|
||||
|
||||
Reference in New Issue
Block a user