mirror of
https://github.com/7836246/cursor2api.git
synced 2026-05-18 12:36:58 +08:00
Tighten image path normalization, preserve multimodal request content across OpenAI-compatible endpoints, and fail fast on unsupported image_file inputs so clients get predictable behavior instead of silent degradation. Made-with: Cursor
144 lines
4.4 KiB
JavaScript
144 lines
4.4 KiB
JavaScript
/**
|
||
* test/unit-openai-chat-input.mjs
|
||
*
|
||
* 单元测试:/v1/chat/completions 输入内容块兼容性
|
||
* 运行方式:node test/unit-openai-chat-input.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');
|
||
}
|
||
|
||
function assertEqual(a, b, msg) {
|
||
const as = JSON.stringify(a), bs = JSON.stringify(b);
|
||
if (as !== bs) throw new Error(msg || `Expected ${bs}, got ${as}`);
|
||
}
|
||
|
||
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') && p.text) {
|
||
blocks.push({ type: 'text', text: p.text });
|
||
} else if (p.type === 'image_url' && p.image_url?.url) {
|
||
blocks.push({
|
||
type: 'image',
|
||
source: { type: 'url', media_type: 'image/jpeg', data: p.image_url.url },
|
||
});
|
||
} else if (p.type === 'input_image' && p.image_url?.url) {
|
||
blocks.push({
|
||
type: 'image',
|
||
source: { type: 'url', media_type: 'image/jpeg', data: p.image_url.url },
|
||
});
|
||
}
|
||
}
|
||
return blocks.length > 0 ? blocks : '';
|
||
}
|
||
return String(msg.content);
|
||
}
|
||
|
||
function extractOpenAIContent(msg) {
|
||
const blocks = extractOpenAIContentBlocks(msg);
|
||
if (typeof blocks === 'string') return blocks;
|
||
return blocks.filter(b => b.type === 'text').map(b => b.text).join('\n');
|
||
}
|
||
|
||
function convertToAnthropicRequest(body) {
|
||
const rawMessages = [];
|
||
let systemPrompt;
|
||
|
||
for (const msg of body.messages) {
|
||
switch (msg.role) {
|
||
case 'system':
|
||
systemPrompt = (systemPrompt ? systemPrompt + '\n\n' : '') + extractOpenAIContent(msg);
|
||
break;
|
||
case 'user': {
|
||
const contentBlocks = extractOpenAIContentBlocks(msg);
|
||
rawMessages.push({
|
||
role: 'user',
|
||
content: Array.isArray(contentBlocks) ? contentBlocks : (contentBlocks || ''),
|
||
});
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
return {
|
||
system: systemPrompt,
|
||
messages: rawMessages,
|
||
};
|
||
}
|
||
|
||
console.log('\n📦 [1] chat.completions input_text 兼容\n');
|
||
|
||
test('user input_text 不应丢失', () => {
|
||
const req = convertToAnthropicRequest({
|
||
model: 'gpt-4.1',
|
||
messages: [{
|
||
role: 'user',
|
||
content: [
|
||
{ type: 'input_text', text: '请描述这张图' },
|
||
{ type: 'input_image', image_url: { url: 'https://example.com/a.jpg' } },
|
||
],
|
||
}],
|
||
});
|
||
|
||
assertEqual(req.messages.length, 1);
|
||
assert(Array.isArray(req.messages[0].content), 'content should be block array');
|
||
assertEqual(req.messages[0].content[0], { type: 'text', text: '请描述这张图' });
|
||
assertEqual(req.messages[0].content[1].type, 'image');
|
||
});
|
||
|
||
test('system input_text 应拼接进 system prompt', () => {
|
||
const req = convertToAnthropicRequest({
|
||
model: 'gpt-4.1',
|
||
messages: [{
|
||
role: 'system',
|
||
content: [
|
||
{ type: 'input_text', text: '你是一个严谨的助手。' },
|
||
{ type: 'input_text', text: '请直接回答。' },
|
||
],
|
||
}, {
|
||
role: 'user',
|
||
content: 'hi',
|
||
}],
|
||
});
|
||
|
||
assertEqual(req.system, '你是一个严谨的助手。\n请直接回答。');
|
||
});
|
||
|
||
test('传统 text 块仍然兼容', () => {
|
||
const req = convertToAnthropicRequest({
|
||
model: 'gpt-4.1',
|
||
messages: [{
|
||
role: 'user',
|
||
content: [{ type: 'text', text: 'hello' }],
|
||
}],
|
||
});
|
||
|
||
assertEqual(req.messages[0].content[0], { type: 'text', text: 'hello' });
|
||
});
|
||
|
||
console.log('\n' + '═'.repeat(55));
|
||
console.log(` 结果: ${passed} 通过 / ${failed} 失败 / ${passed + failed} 总计`);
|
||
console.log('═'.repeat(55) + '\n');
|
||
|
||
if (failed > 0) process.exit(1);
|