refactor(open-sse): translator DRY + schema enums, bug fixes, dead code cleanup
- Bug B1-B7: media UI m.kind||m.type, serviceKinds, gemini mediaPriority, schema kind, models/info lookup by kind - Dead code D1-D6: safeParseJSON, drop PROVIDER_ENDPOINTS, orphan fetcher, GITHUB_CONFIG derive, getProviderConfig internal, legacy kiro file - Translator concerns: toOpenAIUsage, toOpenAIFinish (gemini/kiro/ollama + fix kiro tool finish), thinking effort maps - Reorg helpers/ → concerns/ (logic) + formats/ (per-format) + schema/ (pure enums: roles/blocks/finishReasons/defaults) - Wire ~280 hardcoded role/block/finish/default literals to schema enums across 20+ files - collapseTextParts + extractTextContent dedup - Normalize translator fn names to openaiToXRequest / xToOpenAIResponse - Golden tests lock behavior; 0 regression (byte-for-byte providers/alias, 26=26 known fails) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
130
open-sse/translator/formats/openai.js
Normal file
130
open-sse/translator/formats/openai.js
Normal file
@@ -0,0 +1,130 @@
|
||||
// OpenAI helper functions for translator
|
||||
import { ROLE, OPENAI_BLOCK, CLAUDE_BLOCK, VALID_OPENAI_CONTENT_TYPES, VALID_OPENAI_MESSAGE_TYPES } from "../schema/index.js";
|
||||
|
||||
// Re-export valid-type lists (moved to schema/blocks.js) to keep existing importers working.
|
||||
export { VALID_OPENAI_CONTENT_TYPES, VALID_OPENAI_MESSAGE_TYPES };
|
||||
|
||||
// Filter messages to OpenAI standard format
|
||||
// Remove: thinking, redacted_thinking, signature, and other non-OpenAI blocks
|
||||
export function filterToOpenAIFormat(body) {
|
||||
if (!body.messages || !Array.isArray(body.messages)) return body;
|
||||
|
||||
body.messages = body.messages.map(msg => {
|
||||
// Normalize developer role to system (many providers don't support developer)
|
||||
if (msg.role === ROLE.DEVELOPER) msg = { ...msg, role: ROLE.SYSTEM };
|
||||
|
||||
// Keep tool messages as-is (OpenAI format)
|
||||
if (msg.role === ROLE.TOOL) return msg;
|
||||
|
||||
// Keep assistant messages with tool_calls as-is
|
||||
if (msg.role === ROLE.ASSISTANT && msg.tool_calls) return msg;
|
||||
|
||||
// Handle string content
|
||||
if (typeof msg.content === "string") return msg;
|
||||
|
||||
// Handle array content
|
||||
if (Array.isArray(msg.content)) {
|
||||
const filteredContent = [];
|
||||
|
||||
for (const block of msg.content) {
|
||||
// Skip thinking blocks
|
||||
if (block.type === CLAUDE_BLOCK.THINKING || block.type === CLAUDE_BLOCK.REDACTED_THINKING) continue;
|
||||
|
||||
// Only keep valid OpenAI content types
|
||||
if (VALID_OPENAI_CONTENT_TYPES.includes(block.type)) {
|
||||
// Remove signature field if exists
|
||||
const { signature, cache_control, ...cleanBlock } = block;
|
||||
filteredContent.push(cleanBlock);
|
||||
} else if (block.type === CLAUDE_BLOCK.TOOL_USE) {
|
||||
// Convert tool_use to tool_calls format (handled separately)
|
||||
continue;
|
||||
} else if (block.type === CLAUDE_BLOCK.TOOL_RESULT) {
|
||||
// Keep tool_result but clean it
|
||||
const { signature, cache_control, ...cleanBlock } = block;
|
||||
filteredContent.push(cleanBlock);
|
||||
}
|
||||
}
|
||||
|
||||
// If all content was filtered, add empty text
|
||||
if (filteredContent.length === 0) {
|
||||
filteredContent.push({ type: OPENAI_BLOCK.TEXT, text: "" });
|
||||
}
|
||||
|
||||
return { ...msg, content: filteredContent };
|
||||
}
|
||||
|
||||
return msg;
|
||||
});
|
||||
|
||||
// Filter out messages with only empty text (but NEVER filter tool messages)
|
||||
body.messages = body.messages.filter(msg => {
|
||||
// Always keep tool messages
|
||||
if (msg.role === ROLE.TOOL) return true;
|
||||
// Always keep assistant messages with tool_calls
|
||||
if (msg.role === ROLE.ASSISTANT && msg.tool_calls) return true;
|
||||
|
||||
if (typeof msg.content === "string") return msg.content.trim() !== "";
|
||||
if (Array.isArray(msg.content)) {
|
||||
return msg.content.some(b =>
|
||||
(b.type === OPENAI_BLOCK.TEXT && b.text?.trim()) ||
|
||||
b.type !== OPENAI_BLOCK.TEXT
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Remove empty tools array (some providers like QWEN reject it)
|
||||
if (body.tools && Array.isArray(body.tools) && body.tools.length === 0) {
|
||||
delete body.tools;
|
||||
}
|
||||
|
||||
// Normalize tools to OpenAI format (from Claude, Gemini, etc.)
|
||||
if (body.tools && Array.isArray(body.tools) && body.tools.length > 0) {
|
||||
body.tools = body.tools.map(tool => {
|
||||
// Already OpenAI format
|
||||
if (tool.type === OPENAI_BLOCK.FUNCTION && tool.function) return tool;
|
||||
|
||||
// Claude format: {name, description, input_schema}
|
||||
if (tool.name && (tool.input_schema || tool.description)) {
|
||||
return {
|
||||
type: OPENAI_BLOCK.FUNCTION,
|
||||
function: {
|
||||
name: tool.name,
|
||||
description: String(tool.description || ""),
|
||||
parameters: tool.input_schema || { type: "object", properties: {} }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Gemini format: {functionDeclarations: [{name, description, parameters}]}
|
||||
if (tool.functionDeclarations && Array.isArray(tool.functionDeclarations)) {
|
||||
return tool.functionDeclarations.map(fn => ({
|
||||
type: OPENAI_BLOCK.FUNCTION,
|
||||
function: {
|
||||
name: fn.name,
|
||||
description: String(fn.description || ""),
|
||||
parameters: fn.parameters || { type: "object", properties: {} }
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
return tool;
|
||||
}).flat();
|
||||
}
|
||||
|
||||
// Normalize tool_choice to OpenAI format
|
||||
if (body.tool_choice && typeof body.tool_choice === "object") {
|
||||
const choice = body.tool_choice;
|
||||
// Claude format: {type: "auto|any|tool", name?: "..."}
|
||||
if (choice.type === "auto") {
|
||||
body.tool_choice = "auto";
|
||||
} else if (choice.type === "any") {
|
||||
body.tool_choice = "required";
|
||||
} else if (choice.type === "tool" && choice.name) {
|
||||
body.tool_choice = { type: OPENAI_BLOCK.FUNCTION, function: { name: choice.name } };
|
||||
}
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user