File size: 3,425 Bytes
097fb32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/**
 * test/unit-openai-image-file.mjs
 *
 * 单元测试:image_file 输入应显式报错,而不是静默降级
 * 运行方式:node test/unit-openai-image-file.mjs
 */

let passed = 0;
let failed = 0;

function test(name, fn) {
    try {
        fn();
        console.log(`  ✅  ${name}`);
        passed++;
    } catch (e) {
        console.error(`  ❌  ${name}`);
        console.error(`      ${e.message}`);
        failed++;
    }
}

function assert(condition, msg) {
    if (!condition) throw new Error(msg || 'Assertion failed');
}

class OpenAIRequestError extends Error {
    constructor(message, status = 400, type = 'invalid_request_error', code = 'invalid_request') {
        super(message);
        this.name = 'OpenAIRequestError';
        this.status = status;
        this.type = type;
        this.code = code;
    }
}

function unsupportedImageFileError(fileId) {
    const suffix = fileId ? ` (file_id: ${fileId})` : '';
    return new OpenAIRequestError(
        `Unsupported content part: image_file${suffix}. This proxy does not support OpenAI Files API image references. Please send the image as image_url, input_image, data URI, or a local file path instead.`,
        400,
        'invalid_request_error',
        'unsupported_content_part'
    );
}

function extractOpenAIContentBlocks(msg) {
    if (msg.content === null || msg.content === undefined) return '';
    if (typeof msg.content === 'string') return msg.content;
    if (Array.isArray(msg.content)) {
        const blocks = [];
        for (const p of msg.content) {
            if (p.type === 'text' || p.type === 'input_text') {
                if (p.text) blocks.push({ type: 'text', text: p.text });
            } else if (p.type === 'image_file' && p.image_file) {
                throw unsupportedImageFileError(p.image_file.file_id);
            }
        }
        return blocks.length > 0 ? blocks : '';
    }
    return String(msg.content);
}

console.log('\n📦 [1] image_file 显式报错\n');

test('image_file 应抛出 OpenAIRequestError', () => {
    let thrown;
    try {
        extractOpenAIContentBlocks({
            role: 'user',
            content: [
                { type: 'input_text', text: '请描述图片' },
                { type: 'image_file', image_file: { file_id: 'file_123' } },
            ],
        });
    } catch (e) {
        thrown = e;
    }

    assert(thrown instanceof OpenAIRequestError, 'should throw OpenAIRequestError');
    assert(thrown.message.includes('image_file'), 'message should mention image_file');
    assert(thrown.message.includes('file_123'), 'message should include file_id');
    assert(thrown.status === 400, 'status should be 400');
    assert(thrown.type === 'invalid_request_error', 'type should be invalid_request_error');
    assert(thrown.code === 'unsupported_content_part', 'code should be unsupported_content_part');
});

test('普通文本块仍可正常通过', () => {
    const blocks = extractOpenAIContentBlocks({
        role: 'user',
        content: [{ type: 'input_text', text: 'hello' }],
    });
    assert(Array.isArray(blocks), 'blocks should be array');
    assert(blocks[0].text === 'hello', 'text block should remain intact');
});

console.log('\n' + '═'.repeat(55));
console.log(`  结果: ${passed} 通过 / ${failed} 失败 / ${passed + failed} 总计`);
console.log('═'.repeat(55) + '\n');

if (failed > 0) process.exit(1);