* Strip non-text blocks from is_error tool_results — the API rejects the * combination with "all content must be type text if is_error is true". * * Read-side guard for transcripts persisted before smooshIntoToolResult * learned to filter on is_error. Without this a resumed session with one * o
( messages: (UserMessage | AssistantMessage)[], )
| 1882 | * text left behind by a stripped image is re-merged. |
| 1883 | */ |
| 1884 | function sanitizeErrorToolResultContent( |
| 1885 | messages: (UserMessage | AssistantMessage)[], |
| 1886 | ): (UserMessage | AssistantMessage)[] { |
| 1887 | return messages.map(msg => { |
| 1888 | if (msg.type !== 'user') return msg |
| 1889 | const content = msg.message.content |
| 1890 | if (!Array.isArray(content)) return msg |
| 1891 | |
| 1892 | let changed = false |
| 1893 | const newContent = content.map(b => { |
| 1894 | if (b.type !== 'tool_result' || !b.is_error) return b |
| 1895 | const trContent = b.content |
| 1896 | if (!Array.isArray(trContent)) return b |
| 1897 | if (trContent.every(c => c.type === 'text')) return b |
| 1898 | changed = true |
| 1899 | const texts = trContent.filter(c => c.type === 'text').map(c => c.text) |
| 1900 | const textOnly: TextBlockParam[] = |
| 1901 | texts.length > 0 ? [{ type: 'text', text: texts.join('\n\n') }] : [] |
| 1902 | return { ...b, content: textOnly } |
| 1903 | }) |
| 1904 | if (!changed) return msg |
| 1905 | return { ...msg, message: { ...msg.message, content: newContent } } |
| 1906 | }) |
| 1907 | } |
| 1908 | |
| 1909 | /** |
| 1910 | * Move text-block siblings off user messages that contain tool_reference. |
no outgoing calls
no test coverage detected