merge: resolve conflicts with origin/master - keep both local and remote features
This commit is contained in:
@@ -37,6 +37,12 @@ export function translateNonStreamingResponse(responseBody, targetFormat, source
|
||||
function: { name: part.functionCall.name, arguments: JSON.stringify(part.functionCall.args || {}) }
|
||||
});
|
||||
}
|
||||
// Handle inline image data (from image generation models)
|
||||
const inlineData = part.inlineData || part.inline_data;
|
||||
if (inlineData?.data) {
|
||||
const mimeType = inlineData.mimeType || inlineData.mime_type || "image/png";
|
||||
textContent += `\n\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +82,12 @@ export function translateNonStreamingResponse(responseBody, targetFormat, source
|
||||
// missing/null (e.g. M3 with max_tokens:1 spends the budget on thinking
|
||||
// and returns `content: null`). Returning the raw body would leave the
|
||||
// OpenAI client without a `choices` array and surface as a UI test error.
|
||||
if (responseBody.content && !Array.isArray(responseBody.content)) return responseBody;
|
||||
// Early return if the response is already in OpenAI format (has choices array)
|
||||
// or if it has content as a non-array value (likely a different non-Claude format).
|
||||
// Some providers (e.g. xiaomi-tokenplan) return OpenAI-format responses even when
|
||||
// the request was translated to Claude format — the targetFormat is Claude but the
|
||||
// actual response is OpenAI-native and needs no further translation.
|
||||
if (responseBody.choices || (responseBody.content && !Array.isArray(responseBody.content))) return responseBody;
|
||||
|
||||
let textContent = "", thinkingContent = "";
|
||||
const toolCalls = [];
|
||||
@@ -208,11 +219,14 @@ export async function handleNonStreamingResponse({ providerResponse, provider, m
|
||||
translatedResponse.usage = filterUsageForFormat(addBufferToUsage(translatedResponse.usage), sourceFormat);
|
||||
}
|
||||
|
||||
// Strip reasoning_content — some clients (e.g. Firecrawl AI SDK) have JSON parsers that
|
||||
// break on this non-standard field, even though OpenAI allows it in extensions.
|
||||
// Strip reasoning_content only when content is non-empty.
|
||||
// When content is empty (e.g. thinking models that used all tokens for reasoning),
|
||||
// reasoning_content is the only useful output and must be preserved.
|
||||
if (translatedResponse?.choices) {
|
||||
for (const choice of translatedResponse.choices) {
|
||||
if (choice?.message) delete choice.message.reasoning_content;
|
||||
if (choice?.message?.reasoning_content && choice.message.content) {
|
||||
delete choice.message.reasoning_content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,11 @@ import { convertResponsesStreamToJson } from "../../transformer/streamToJsonConv
|
||||
import { createErrorResult } from "../../utils/error.js";
|
||||
import { HTTP_STATUS } from "../../config/runtimeConfig.js";
|
||||
import { FORMATS } from "../../translator/formats.js";
|
||||
import { PROVIDERS } from "../../config/providers.js";
|
||||
import { buildRequestDetail, extractRequestConfig, saveUsageStats } from "./requestDetail.js";
|
||||
|
||||
// Responses-API providers (e.g. codex) may emit SSE without content-type + use Responses output shape
|
||||
const isResponsesProvider = (p) => PROVIDERS[p]?.format === FORMATS.OPENAI_RESPONSES;
|
||||
import { saveRequestDetail, appendRequestLog } from "@/lib/usageDb.js";
|
||||
|
||||
function textFromResponsesMessageItem(item) {
|
||||
@@ -100,7 +104,7 @@ export function parseSSEToOpenAIResponse(rawSSE, fallbackModel) {
|
||||
*/
|
||||
export async function handleForcedSSEToJson({ providerResponse, sourceFormat, provider, model, body, stream, translatedBody, finalBody, requestStartTime, connectionId, apiKey, clientRawRequest, onRequestSuccess, trackDone, appendLog }) {
|
||||
const contentType = providerResponse.headers.get("content-type") || "";
|
||||
const isSSE = contentType.includes("text/event-stream") || (contentType === "" && provider === "codex");
|
||||
const isSSE = contentType.includes("text/event-stream") || (contentType === "" && isResponsesProvider(provider));
|
||||
if (!isSSE) return null; // not handled here
|
||||
|
||||
trackDone();
|
||||
@@ -112,7 +116,7 @@ export async function handleForcedSSEToJson({ providerResponse, sourceFormat, pr
|
||||
};
|
||||
|
||||
// Codex/Responses API SSE path
|
||||
const isCodexResponsesApi = provider === "codex" || sourceFormat === FORMATS.OPENAI_RESPONSES;
|
||||
const isCodexResponsesApi = isResponsesProvider(provider) || sourceFormat === FORMATS.OPENAI_RESPONSES;
|
||||
if (isCodexResponsesApi) {
|
||||
try {
|
||||
const jsonResponse = await convertResponsesStreamToJson(providerResponse.body);
|
||||
|
||||
@@ -7,12 +7,16 @@ import { STREAM_STALL_TIMEOUT_MS } from "../../config/runtimeConfig.js";
|
||||
import { buildAbortedResponsesTerminalBytes } from "../../utils/responsesStreamHelpers.js";
|
||||
import { buildRequestDetail, extractRequestConfig } from "./requestDetail.js";
|
||||
import { saveRequestDetail } from "@/lib/usageDb.js";
|
||||
import { SSE_HEADERS_CORS as SSE_HEADERS } from "../../utils/sseConstants.js";
|
||||
|
||||
const SSE_HEADERS = {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
// Codex returns Responses API SSE → which client format to translate INTO, by request sourceFormat.
|
||||
// Gemini-family all map to ANTIGRAVITY decoder; unknown sources fall back to OPENAI.
|
||||
const CODEX_SOURCE_TO_TARGET = {
|
||||
[FORMATS.OPENAI_RESPONSES]: FORMATS.OPENAI_RESPONSES,
|
||||
[FORMATS.CLAUDE]: FORMATS.CLAUDE,
|
||||
[FORMATS.ANTIGRAVITY]: FORMATS.ANTIGRAVITY,
|
||||
[FORMATS.GEMINI]: FORMATS.ANTIGRAVITY,
|
||||
[FORMATS.GEMINI_CLI]: FORMATS.ANTIGRAVITY,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -20,15 +24,12 @@ const SSE_HEADERS = {
|
||||
*/
|
||||
function buildTransformStream({ provider, sourceFormat, targetFormat, userAgent, reqLogger, toolNameMap, model, connectionId, body, onStreamComplete, apiKey }) {
|
||||
const isDroidCLI = userAgent?.toLowerCase().includes("droid") || userAgent?.toLowerCase().includes("codex-cli");
|
||||
const needsCodexTranslation = provider === "codex" && targetFormat === FORMATS.OPENAI_RESPONSES && !isDroidCLI;
|
||||
// Responses-API providers (e.g. codex) emit Responses SSE → translate into client format
|
||||
const isResponsesProvider = PROVIDERS[provider]?.format === FORMATS.OPENAI_RESPONSES;
|
||||
const needsCodexTranslation = isResponsesProvider && targetFormat === FORMATS.OPENAI_RESPONSES && !isDroidCLI;
|
||||
|
||||
if (needsCodexTranslation) {
|
||||
// Codex returns Responses API SSE → translate to client format
|
||||
let codexTarget;
|
||||
if (sourceFormat === FORMATS.OPENAI_RESPONSES) codexTarget = FORMATS.OPENAI_RESPONSES;
|
||||
else if (sourceFormat === FORMATS.CLAUDE) codexTarget = FORMATS.CLAUDE;
|
||||
else if (sourceFormat === FORMATS.ANTIGRAVITY || sourceFormat === FORMATS.GEMINI || sourceFormat === FORMATS.GEMINI_CLI) codexTarget = FORMATS.ANTIGRAVITY;
|
||||
else codexTarget = FORMATS.OPENAI;
|
||||
const codexTarget = CODEX_SOURCE_TO_TARGET[sourceFormat] || FORMATS.OPENAI;
|
||||
return createSSETransformStreamWithLogger(FORMATS.OPENAI_RESPONSES, codexTarget, provider, reqLogger, toolNameMap, model, connectionId, body, onStreamComplete, apiKey);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user