(toolBlock: ToolContentBlock)
| 233 | * For proposed tools (implementors): construct diff from input replacements. |
| 234 | */ |
| 235 | export function extractDiff(toolBlock: ToolContentBlock): string | null { |
| 236 | let hasSuccessfulOutput = false |
| 237 | |
| 238 | // First try to get from outputRaw (for executed tool results) |
| 239 | // outputRaw is typically an array like [{type: "json", value: {unifiedDiff: "..."}}] |
| 240 | const outputRaw = toolBlock.outputRaw as unknown |
| 241 | if (Array.isArray(outputRaw) && outputRaw[0]?.value) { |
| 242 | const value = outputRaw[0].value as Record<string, unknown> |
| 243 | if (hasErrorMessage(value)) return null |
| 244 | if (isSuccessfulEditMessage(value.message)) hasSuccessfulOutput = true |
| 245 | if (value.unifiedDiff) return value.unifiedDiff as string |
| 246 | if (value.patch) return value.patch as string |
| 247 | } |
| 248 | // Also check direct properties (in case format differs) |
| 249 | if (typeof outputRaw === 'object' && outputRaw !== null) { |
| 250 | const rawObj = outputRaw as Record<string, unknown> |
| 251 | if (hasErrorMessage(rawObj)) return null |
| 252 | if (isSuccessfulEditMessage(rawObj.message)) hasSuccessfulOutput = true |
| 253 | if (rawObj.unifiedDiff) return rawObj.unifiedDiff as string |
| 254 | if (rawObj.patch) return rawObj.patch as string |
| 255 | } |
| 256 | |
| 257 | // Try to get from output string (key: value format) |
| 258 | const outputStr = typeof toolBlock.output === 'string' ? toolBlock.output : '' |
| 259 | const message = extractValueForKey(outputStr, 'message') |
| 260 | const diffFromOutput = |
| 261 | extractValueForKey(outputStr, 'unifiedDiff') || |
| 262 | extractValueForKey(outputStr, 'patch') |
| 263 | |
| 264 | if (hasFailedEditOutput({ outputStr, message, diffFromOutput })) { |
| 265 | return null |
| 266 | } |
| 267 | if (isSuccessfulEditMessage(message)) { |
| 268 | hasSuccessfulOutput = true |
| 269 | } |
| 270 | |
| 271 | if (diffFromOutput) { |
| 272 | return diffFromOutput |
| 273 | } |
| 274 | |
| 275 | // For proposed/pending edits, or confirmed successful executions, construct |
| 276 | // the preview from input when the result omits a diff. |
| 277 | const canUseInputFallback = |
| 278 | isProposedToolName(toolBlock.toolName) || |
| 279 | outputStr === '' || |
| 280 | hasSuccessfulOutput |
| 281 | if (!canUseInputFallback) { |
| 282 | return null |
| 283 | } |
| 284 | |
| 285 | const input = toolBlock.input as Record<string, unknown> |
| 286 | const baseToolName = getBaseToolName(toolBlock.toolName) |
| 287 | |
| 288 | // Handle str_replace: construct diff from replacements |
| 289 | if (baseToolName === 'str_replace' && Array.isArray(input?.replacements)) { |
| 290 | const replacements = input.replacements as ReplacementInput[] |
| 291 | if (replacements.length > 0) { |
| 292 | return constructDiffFromReplacements(replacements) |
no test coverage detected