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:
@@ -1,7 +1,9 @@
|
||||
import { register } from "../index.js";
|
||||
import { FORMATS } from "../formats.js";
|
||||
import { adjustMaxTokens } from "../helpers/maxTokensHelper.js";
|
||||
import { encodeDataUri } from "../helpers/imageHelper.js";
|
||||
import { adjustMaxTokens } from "../formats/maxTokens.js";
|
||||
import { encodeDataUri } from "../concerns/image.js";
|
||||
import { ROLE, OPENAI_BLOCK, CLAUDE_BLOCK } from "../schema/index.js";
|
||||
import { collapseTextParts } from "../concerns/message.js";
|
||||
|
||||
function stripAnthropicBillingHeader(text) {
|
||||
if (typeof text !== "string") return "";
|
||||
@@ -34,7 +36,7 @@ export function claudeToOpenAIRequest(model, body, stream) {
|
||||
|
||||
if (systemContent) {
|
||||
result.messages.push({
|
||||
role: "system",
|
||||
role: ROLE.SYSTEM,
|
||||
content: systemContent
|
||||
});
|
||||
}
|
||||
@@ -58,13 +60,13 @@ export function claudeToOpenAIRequest(model, body, stream) {
|
||||
|
||||
// Fix missing tool responses - OpenAI requires every tool_call to have a response.
|
||||
// Local variant: scans contiguous tool replies + inserts "[No response received]"
|
||||
// (distinct from the global immediate-next check in toolCallHelper, runs on the openai leg).
|
||||
// (distinct from the global immediate-next check in concerns/toolCall, runs on the openai leg).
|
||||
fixMissingToolResponsesOpenAI(result.messages);
|
||||
|
||||
// Tools
|
||||
if (body.tools && Array.isArray(body.tools)) {
|
||||
result.tools = body.tools.map(tool => ({
|
||||
type: "function",
|
||||
type: OPENAI_BLOCK.FUNCTION,
|
||||
function: {
|
||||
name: tool.name,
|
||||
description: String(tool.description || ""),
|
||||
@@ -85,7 +87,7 @@ export function claudeToOpenAIRequest(model, body, stream) {
|
||||
function fixMissingToolResponsesOpenAI(messages) {
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const msg = messages[i];
|
||||
if (msg.role === "assistant" && msg.tool_calls && msg.tool_calls.length > 0) {
|
||||
if (msg.role === ROLE.ASSISTANT && msg.tool_calls && msg.tool_calls.length > 0) {
|
||||
const toolCallIds = msg.tool_calls.map(tc => tc.id);
|
||||
|
||||
// Collect all tool response IDs that IMMEDIATELY follow this assistant message
|
||||
@@ -93,7 +95,7 @@ function fixMissingToolResponsesOpenAI(messages) {
|
||||
let insertPosition = i + 1;
|
||||
for (let j = i + 1; j < messages.length; j++) {
|
||||
const nextMsg = messages[j];
|
||||
if (nextMsg.role === "tool" && nextMsg.tool_call_id) {
|
||||
if (nextMsg.role === ROLE.TOOL && nextMsg.tool_call_id) {
|
||||
respondedIds.add(nextMsg.tool_call_id);
|
||||
insertPosition = j + 1;
|
||||
} else {
|
||||
@@ -106,7 +108,7 @@ function fixMissingToolResponsesOpenAI(messages) {
|
||||
|
||||
if (missingIds.length > 0) {
|
||||
const missingResponses = missingIds.map(id => ({
|
||||
role: "tool",
|
||||
role: ROLE.TOOL,
|
||||
tool_call_id: id,
|
||||
content: "[No response received]"
|
||||
}));
|
||||
@@ -119,7 +121,7 @@ function fixMissingToolResponsesOpenAI(messages) {
|
||||
|
||||
// Convert single Claude message - returns single message or array of messages
|
||||
function convertClaudeMessage(msg) {
|
||||
const role = msg.role === "user" || msg.role === "tool" ? "user" : "assistant";
|
||||
const role = msg.role === ROLE.USER || msg.role === ROLE.TOOL ? ROLE.USER : ROLE.ASSISTANT;
|
||||
|
||||
// Simple string content
|
||||
if (typeof msg.content === "string") {
|
||||
@@ -134,14 +136,14 @@ function convertClaudeMessage(msg) {
|
||||
|
||||
for (const block of msg.content) {
|
||||
switch (block.type) {
|
||||
case "text":
|
||||
parts.push({ type: "text", text: block.text });
|
||||
case CLAUDE_BLOCK.TEXT:
|
||||
parts.push({ type: OPENAI_BLOCK.TEXT, text: block.text });
|
||||
break;
|
||||
|
||||
case "image":
|
||||
case CLAUDE_BLOCK.IMAGE:
|
||||
if (block.source?.type === "base64") {
|
||||
parts.push({
|
||||
type: "image_url",
|
||||
type: OPENAI_BLOCK.IMAGE_URL,
|
||||
image_url: {
|
||||
url: encodeDataUri(block.source.media_type, block.source.data)
|
||||
}
|
||||
@@ -149,10 +151,10 @@ function convertClaudeMessage(msg) {
|
||||
}
|
||||
break;
|
||||
|
||||
case "tool_use":
|
||||
case CLAUDE_BLOCK.TOOL_USE:
|
||||
toolCalls.push({
|
||||
id: block.id,
|
||||
type: "function",
|
||||
type: OPENAI_BLOCK.FUNCTION,
|
||||
function: {
|
||||
name: block.name,
|
||||
arguments: JSON.stringify(block.input || {})
|
||||
@@ -160,13 +162,13 @@ function convertClaudeMessage(msg) {
|
||||
});
|
||||
break;
|
||||
|
||||
case "tool_result":
|
||||
case CLAUDE_BLOCK.TOOL_RESULT:
|
||||
let resultContent = "";
|
||||
if (typeof block.content === "string") {
|
||||
resultContent = block.content;
|
||||
} else if (Array.isArray(block.content)) {
|
||||
resultContent = block.content
|
||||
.filter(c => c.type === "text")
|
||||
.filter(c => c.type === CLAUDE_BLOCK.TEXT)
|
||||
.map(c => c.text)
|
||||
.join("\n") || JSON.stringify(block.content);
|
||||
} else if (block.content) {
|
||||
@@ -174,7 +176,7 @@ function convertClaudeMessage(msg) {
|
||||
}
|
||||
|
||||
toolResults.push({
|
||||
role: "tool",
|
||||
role: ROLE.TOOL,
|
||||
tool_call_id: block.tool_use_id,
|
||||
content: resultContent
|
||||
});
|
||||
@@ -185,21 +187,16 @@ function convertClaudeMessage(msg) {
|
||||
// If has tool results, return array of tool messages
|
||||
if (toolResults.length > 0) {
|
||||
if (parts.length > 0) {
|
||||
const textContent = parts.length === 1 && parts[0].type === "text"
|
||||
? parts[0].text
|
||||
: parts;
|
||||
return [...toolResults, { role: "user", content: textContent }];
|
||||
return [...toolResults, { role: ROLE.USER, content: collapseTextParts(parts) }];
|
||||
}
|
||||
return toolResults;
|
||||
}
|
||||
|
||||
// If has tool calls, return assistant message with tool_calls
|
||||
if (toolCalls.length > 0) {
|
||||
const result = { role: "assistant" };
|
||||
const result = { role: ROLE.ASSISTANT };
|
||||
if (parts.length > 0) {
|
||||
result.content = parts.length === 1 && parts[0].type === "text"
|
||||
? parts[0].text
|
||||
: parts;
|
||||
result.content = collapseTextParts(parts);
|
||||
}
|
||||
result.tool_calls = toolCalls;
|
||||
return result;
|
||||
@@ -209,7 +206,7 @@ function convertClaudeMessage(msg) {
|
||||
if (parts.length > 0) {
|
||||
return {
|
||||
role,
|
||||
content: parts.length === 1 && parts[0].type === "text" ? parts[0].text : parts
|
||||
content: collapseTextParts(parts)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -230,7 +227,7 @@ function convertToolChoice(choice) {
|
||||
switch (choice.type) {
|
||||
case "auto": return "auto";
|
||||
case "any": return "required";
|
||||
case "tool": return { type: "function", function: { name: choice.name } };
|
||||
case "tool": return { type: OPENAI_BLOCK.FUNCTION, function: { name: choice.name } };
|
||||
default: return "auto";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user