File size: 3,005 Bytes
077865a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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