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:
decolua
2026-06-14 18:49:38 +07:00
parent c5c9061eac
commit d3f61aac2f
145 changed files with 1252 additions and 1160 deletions

View File

@@ -12,7 +12,9 @@ import {
KIRO_AGENTIC_SYSTEM_PROMPT,
resolveDefaultProfileArn
} from "../../config/kiroConstants.js";
import { parseDataUri } from "../helpers/imageHelper.js";
import { parseDataUri } from "../concerns/image.js";
import { DEFAULT_IMAGE_MIME } from "../schema/index.js";
import { ROLE, OPENAI_BLOCK, CLAUDE_BLOCK } from "../schema/index.js";
/** Render a single tool call as a readable text line. */
function toolCallToText(name, input) {
@@ -56,18 +58,18 @@ function flattenToolInteractions(messages) {
for (const msg of messages) {
// OpenAI tool-result message → user text line
if (msg.role === "tool") {
out.push({ role: "user", content: toolResultToText(msg.content) });
if (msg.role === ROLE.TOOL) {
out.push({ role: ROLE.USER, content: toolResultToText(msg.content) });
continue;
}
if (msg.role === "assistant") {
if (msg.role === ROLE.ASSISTANT) {
const parts = [];
if (Array.isArray(msg.content)) {
for (const c of msg.content) {
if (c.type === "tool_use") {
if (c.type === CLAUDE_BLOCK.TOOL_USE) {
parts.push(toolCallToText(c.name, c.input));
} else if (c.type === "text" || c.text) {
} else if (c.type === OPENAI_BLOCK.TEXT || c.text) {
parts.push(c.text || "");
}
}
@@ -77,15 +79,15 @@ function flattenToolInteractions(messages) {
for (const tc of msg.tool_calls || []) {
parts.push(toolCallToText(tc.function?.name, tc.function?.arguments));
}
out.push({ role: "assistant", content: parts.filter(Boolean).join("\n") });
out.push({ role: ROLE.ASSISTANT, content: parts.filter(Boolean).join("\n") });
continue;
}
// User messages: replace tool_result blocks with text, keep text + images.
if (msg.role === "user" && Array.isArray(msg.content)) {
if (msg.role === ROLE.USER && Array.isArray(msg.content)) {
const newContent = msg.content.map(c =>
c.type === "tool_result"
? { type: "text", text: toolResultToText(c.content) }
c.type === CLAUDE_BLOCK.TOOL_RESULT
? { type: OPENAI_BLOCK.TEXT, text: toolResultToText(c.content) }
: c
);
out.push({ ...msg, content: newContent });
@@ -267,8 +269,8 @@ function convertMessages(messages, tools, model) {
let role = msg.role;
// Normalize: system/tool -> user
if (role === "system" || role === "tool") {
role = "user";
if (role === ROLE.SYSTEM || role === ROLE.TOOL) {
role = ROLE.USER;
}
// If role changes, flush pending
@@ -277,7 +279,7 @@ function convertMessages(messages, tools, model) {
}
currentRole = role;
if (role === "user") {
if (role === ROLE.USER) {
// Extract content
let content = "";
if (typeof msg.content === "string") {
@@ -285,9 +287,9 @@ function convertMessages(messages, tools, model) {
} else if (Array.isArray(msg.content)) {
const textParts = [];
for (const c of msg.content) {
if (c.type === "text" || c.text) {
if (c.type === OPENAI_BLOCK.TEXT || c.text) {
textParts.push(c.text || "");
} else if (c.type === "image_url") {
} else if (c.type === OPENAI_BLOCK.IMAGE_URL) {
// OpenAI format: image_url.url with data URI
const url = c.image_url?.url || "";
const parsed = parseDataUri(url);
@@ -298,10 +300,10 @@ function convertMessages(messages, tools, model) {
// Kiro only supports base64 — fallback to URL text
textParts.push(`[Image: ${url}]`);
}
} else if (c.type === "image") {
} else if (c.type === CLAUDE_BLOCK.IMAGE) {
// Claude format: source.type = "base64", source.media_type, source.data
if (c.source?.type === "base64" && c.source?.data) {
const mediaType = c.source.media_type || "image/png";
const mediaType = c.source.media_type || DEFAULT_IMAGE_MIME;
const format = mediaType.split("/")[1] || mediaType;
pendingImages.push({ format, source: { bytes: c.source.data } });
}
@@ -310,7 +312,7 @@ function convertMessages(messages, tools, model) {
content = textParts.join("\n");
// Check for tool_result blocks
const toolResultBlocks = msg.content.filter(c => c.type === "tool_result");
const toolResultBlocks = msg.content.filter(c => c.type === CLAUDE_BLOCK.TOOL_RESULT);
if (toolResultBlocks.length > 0) {
toolResultBlocks.forEach(block => {
const text = Array.isArray(block.content)
@@ -327,7 +329,7 @@ function convertMessages(messages, tools, model) {
}
// Handle tool role (from normalized)
if (msg.role === "tool") {
if (msg.role === ROLE.TOOL) {
const toolContent = typeof msg.content === "string" ? msg.content : "";
pendingToolResults.push({
toolUseId: msg.tool_call_id,
@@ -337,16 +339,16 @@ function convertMessages(messages, tools, model) {
} else if (content) {
pendingUserContent.push(content);
}
} else if (role === "assistant") {
} else if (role === ROLE.ASSISTANT) {
// Extract text content and tool uses
let textContent = "";
let toolUses = [];
if (Array.isArray(msg.content)) {
const textBlocks = msg.content.filter(c => c.type === "text");
const textBlocks = msg.content.filter(c => c.type === OPENAI_BLOCK.TEXT);
textContent = textBlocks.map(b => b.text).join("\n").trim();
const toolUseBlocks = msg.content.filter(c => c.type === "tool_use");
const toolUseBlocks = msg.content.filter(c => c.type === CLAUDE_BLOCK.TOOL_USE);
toolUses = toolUseBlocks;
} else if (typeof msg.content === "string") {
textContent = msg.content.trim();
@@ -509,7 +511,7 @@ function convertMessages(messages, tools, model) {
* `thinking`, OpenAI `reasoning_effort`, AMP/Cursor magic tags, and model
* name hints.
*/
export function buildKiroPayload(model, body, stream, credentials) {
export function openaiToKiroRequest(model, body, stream, credentials) {
const messages = body.messages || [];
const tools = body.tools || [];
const maxTokens = 32000;
@@ -582,4 +584,4 @@ export function buildKiroPayload(model, body, stream, credentials) {
return payload;
}
register(FORMATS.OPENAI, FORMATS.KIRO, buildKiroPayload, null);
register(FORMATS.OPENAI, FORMATS.KIRO, openaiToKiroRequest, null);