( messages: Message[], tools: Tools = [], )
| 2006 | } |
| 2007 | |
| 2008 | export function normalizeMessagesForAPI( |
| 2009 | messages: Message[], |
| 2010 | tools: Tools = [], |
| 2011 | ): (UserMessage | AssistantMessage)[] { |
| 2012 | // Build set of available tool names for filtering unavailable tool references |
| 2013 | const availableToolNames = new Set(tools.map(t => t.name)) |
| 2014 | |
| 2015 | // First, reorder attachments to bubble up until they hit a tool result or assistant message |
| 2016 | // Then strip virtual messages — they're display-only (e.g. REPL inner tool |
| 2017 | // calls) and must never reach the API. |
| 2018 | const reorderedMessages = reorderAttachmentsForAPI(messages).filter( |
| 2019 | m => !((m.type === 'user' || m.type === 'assistant') && m.isVirtual), |
| 2020 | ) |
| 2021 | |
| 2022 | // Build a map from error text → which block types to strip from the preceding user message. |
| 2023 | const errorToBlockTypes: Record<string, Set<string>> = { |
| 2024 | [getPdfTooLargeErrorMessage()]: new Set(['document']), |
| 2025 | [getPdfPasswordProtectedErrorMessage()]: new Set(['document']), |
| 2026 | [getPdfInvalidErrorMessage()]: new Set(['document']), |
| 2027 | [getImageTooLargeErrorMessage()]: new Set(['image']), |
| 2028 | [getRequestTooLargeErrorMessage()]: new Set(['document', 'image']), |
| 2029 | } |
| 2030 | |
| 2031 | // Walk the reordered messages to build a targeted strip map: |
| 2032 | // userMessageUUID → set of block types to strip from that message. |
| 2033 | const stripTargets = new Map<string, Set<string>>() |
| 2034 | for (let i = 0; i < reorderedMessages.length; i++) { |
| 2035 | const msg = reorderedMessages[i]! |
| 2036 | if (!isSyntheticApiErrorMessage(msg)) { |
| 2037 | continue |
| 2038 | } |
| 2039 | // Determine which error this is |
| 2040 | const errorText = |
| 2041 | Array.isArray(msg.message.content) && |
| 2042 | msg.message.content[0]?.type === 'text' |
| 2043 | ? msg.message.content[0].text |
| 2044 | : undefined |
| 2045 | if (!errorText) { |
| 2046 | continue |
| 2047 | } |
| 2048 | const blockTypesToStrip = errorToBlockTypes[errorText] |
| 2049 | if (!blockTypesToStrip) { |
| 2050 | continue |
| 2051 | } |
| 2052 | // Walk backward to find the nearest preceding isMeta user message |
| 2053 | for (let j = i - 1; j >= 0; j--) { |
| 2054 | const candidate = reorderedMessages[j]! |
| 2055 | if (candidate.type === 'user' && candidate.isMeta) { |
| 2056 | const existing = stripTargets.get(candidate.uuid) |
| 2057 | if (existing) { |
| 2058 | for (const t of blockTypesToStrip) { |
| 2059 | existing.add(t) |
| 2060 | } |
| 2061 | } else { |
| 2062 | stripTargets.set(candidate.uuid, new Set(blockTypesToStrip)) |
| 2063 | } |
| 2064 | break |
| 2065 | } |
no test coverage detected