export function contentToString(content) { if (typeof content === 'string') return content; if (content == null) return ''; if (Array.isArray(content)) { return content .map((b) => { if (typeof b === 'string') return b; const block = b; // OpenAI blocks carry type:'text'; Gemini-lineage agents (Qwen Code, // AionUI) send part-style `{ text }` with no type at all — accept any // block whose `text` is a string and whose type doesn't say it's // something else. (#200) if (typeof block?.text === 'string' && (block.type === 'text' || block.type === undefined)) { return block.text; } return ''; }) .join(''); } return ''; } export function flattenMessageContent(messages) { return messages.map((m) => ({ ...m, content: contentToString(m.content), })); } // True if the content array carries an image block. OpenAI's multimodal // envelope uses `{ type: 'image_url', image_url: { url } }`; some clients send // a bare `{ type: 'image', ... }`. export function contentHasImage(content) { if (!Array.isArray(content)) return false; return content.some((block) => { const type = block?.type; return type === 'image_url' || type === 'image'; }); } // True if any message carries an image content block. Used to route image // requests only to vision-capable models (#118, #125). export function messageHasImage(messages) { return messages.some((m) => contentHasImage(m.content)); } // Normalize the OUTBOUND (provider → client) shape so we honor the OpenAI // contract on the response path the same way `contentToString` does on the // request path. Per spec, `choices[].delta.content` (streaming) and // `choices[].message.content` (non-stream) are strings; some providers // (e.g. Mistral magistral) return an array of content blocks. Forwarding the // array verbatim breaks string-consuming clients ("expected str, got list") // and, mid-stream, drops the turn's tool calls. We coerce array content to a // string while leaving `tool_calls` and every other field untouched. Mutates // and returns the same object (chunks are parsed fresh from JSON per frame, so // in-place mutation is safe). Non-array content passes through unchanged. (#166) export function normalizeOutboundContent(payload) { const choices = payload?.choices; if (!Array.isArray(choices)) return payload; for (const choice of choices) { const delta = choice?.delta; if (delta && Array.isArray(delta.content)) { delta.content = contentToString(delta.content); } const message = choice?.message; if (message && Array.isArray(message.content)) { message.content = contentToString(message.content); } } return payload; } //# sourceMappingURL=content.js.map