Fix bug Tunnel
This commit is contained in:
@@ -367,7 +367,7 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
|
||||
const targetFormat = modelTargetFormat || getTargetFormat(provider);
|
||||
|
||||
// Track if client actually wants streaming (before we force it for providers)
|
||||
const clientRequestedStreaming = body.stream === true;
|
||||
const clientRequestedStreaming = body.stream === true || sourceFormat === FORMATS.ANTIGRAVITY || sourceFormat === FORMATS.GEMINI || sourceFormat === FORMATS.GEMINI_CLI;
|
||||
const providerRequiresStreaming = provider === 'openai' || provider === 'codex';
|
||||
|
||||
// Force streaming for OpenAI/Codex models (they don't support non-streaming mode properly)
|
||||
@@ -411,6 +411,7 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
|
||||
appendRequestLog({ model, provider, connectionId, status: "PENDING" }).catch(() => { });
|
||||
|
||||
const msgCount = translatedBody.messages?.length
|
||||
|| translatedBody.input?.length
|
||||
|| translatedBody.contents?.length
|
||||
|| translatedBody.request?.contents?.length
|
||||
|| 0;
|
||||
@@ -579,13 +580,13 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
|
||||
// (Codex API doesn't always set Content-Type on streaming responses)
|
||||
const isSSEResponse = contentType.includes("text/event-stream") || (contentType === "" && provider === "codex");
|
||||
if (isSSEResponse) {
|
||||
const isResponsesApi = sourceFormat === 'openai-responses';
|
||||
// Codex always returns Responses API SSE format regardless of client source format
|
||||
const isCodexResponsesApi = provider === "codex" || sourceFormat === "openai-responses";
|
||||
|
||||
if (isResponsesApi) {
|
||||
// Responses API SSE → Responses API JSON (for pydantic_ai, OpenAI SDK, etc.)
|
||||
if (isCodexResponsesApi) {
|
||||
// Responses API SSE → parse → translate to client format
|
||||
try {
|
||||
const jsonResponse = await convertResponsesStreamToJson(providerResponse.body);
|
||||
log?.info?.("STREAM", `Converted Responses API SSE → JSON for non-streaming client`);
|
||||
|
||||
if (onRequestSuccess) await onRequestSuccess();
|
||||
|
||||
@@ -606,6 +607,9 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
|
||||
}).catch(() => { });
|
||||
}
|
||||
|
||||
const msgItem = jsonResponse.output?.find(item => item.type === "message");
|
||||
const textContent = msgItem?.content?.find(c => c.type === "output_text")?.text || msgItem?.content?.[0]?.text || null;
|
||||
console.log(`[DBG] codex status=${jsonResponse.status} output.len=${jsonResponse.output?.length} msgItem.type=${msgItem?.type} textLen=${textContent?.length||0}`);
|
||||
const totalLatency = Date.now() - requestStartTime;
|
||||
saveRequestDetail({
|
||||
provider: provider || "unknown",
|
||||
@@ -617,22 +621,67 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
|
||||
request: extractRequestConfig(body, stream),
|
||||
providerRequest: finalBody || translatedBody || null,
|
||||
providerResponse: null,
|
||||
response: {
|
||||
content: jsonResponse.output?.[0]?.content?.[0]?.text || null,
|
||||
thinking: null,
|
||||
finish_reason: jsonResponse.status || "unknown"
|
||||
},
|
||||
response: { content: textContent, thinking: null, finish_reason: jsonResponse.status || "unknown" },
|
||||
status: "success",
|
||||
endpoint: clientRawRequest?.endpoint || null
|
||||
}).catch(() => { });
|
||||
|
||||
// If client is openai-responses → return as-is
|
||||
if (sourceFormat === "openai-responses") {
|
||||
return {
|
||||
success: true,
|
||||
response: new Response(JSON.stringify(jsonResponse), {
|
||||
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// Translate Responses API JSON → OpenAI chat completion JSON
|
||||
const openaiMsg = {
|
||||
id: jsonResponse.id || `chatcmpl-${Date.now()}`,
|
||||
object: "chat.completion",
|
||||
created: jsonResponse.created_at || Math.floor(Date.now() / 1000),
|
||||
model: jsonResponse.model || model,
|
||||
choices: [{
|
||||
index: 0,
|
||||
message: { role: "assistant", content: textContent || "" },
|
||||
finish_reason: jsonResponse.status === "completed" ? "stop" : (jsonResponse.status || "stop")
|
||||
}],
|
||||
usage: {
|
||||
prompt_tokens: usage.input_tokens || 0,
|
||||
completion_tokens: usage.output_tokens || 0,
|
||||
total_tokens: (usage.input_tokens || 0) + (usage.output_tokens || 0)
|
||||
}
|
||||
};
|
||||
|
||||
// Build client-format response based on sourceFormat
|
||||
let finalResp;
|
||||
if (sourceFormat === FORMATS.ANTIGRAVITY || sourceFormat === FORMATS.GEMINI || sourceFormat === FORMATS.GEMINI_CLI) {
|
||||
// Antigravity/Gemini non-streaming format
|
||||
finalResp = {
|
||||
response: {
|
||||
candidates: [{
|
||||
content: { role: "model", parts: [{ text: textContent || "" }] },
|
||||
finishReason: "STOP",
|
||||
index: 0
|
||||
}],
|
||||
usageMetadata: {
|
||||
promptTokenCount: usage.input_tokens || 0,
|
||||
candidatesTokenCount: usage.output_tokens || 0,
|
||||
totalTokenCount: (usage.input_tokens || 0) + (usage.output_tokens || 0)
|
||||
},
|
||||
modelVersion: model,
|
||||
responseId: jsonResponse.id || `resp_${Date.now()}`
|
||||
}
|
||||
};
|
||||
} else {
|
||||
finalResp = openaiMsg;
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
response: new Response(JSON.stringify(jsonResponse), {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
}
|
||||
response: new Response(JSON.stringify(finalResp), {
|
||||
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }
|
||||
})
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -906,9 +955,12 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
|
||||
&& !isDroidCLI;
|
||||
|
||||
if (needsCodexTranslation) {
|
||||
// For claude clients, translate directly to claude format
|
||||
// For openai/openai-responses clients, translate to openai (responsesHandler will re-add event: lines)
|
||||
const codexTarget = sourceFormat === FORMATS.CLAUDE ? FORMATS.CLAUDE : FORMATS.OPENAI;
|
||||
// Translate Codex (openai-responses) SSE → client's source format
|
||||
// Claude → claude, Antigravity/Gemini → antigravity, others → openai
|
||||
let codexTarget;
|
||||
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;
|
||||
log?.debug?.("STREAM", `Codex translation mode: openai-responses → ${codexTarget}`);
|
||||
transformStream = createSSETransformStreamWithLogger('openai-responses', codexTarget, provider, reqLogger, toolNameMap, model, connectionId, body, onStreamComplete, apiKey);
|
||||
} else if (needsTranslation(targetFormat, sourceFormat)) {
|
||||
|
||||
Reference in New Issue
Block a user