end
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
import { translateResponse, initState } from "../translator/index.js";
|
||||
import { FORMATS } from "../translator/formats.js";
|
||||
import { trackPendingRequest, appendRequestLog } from "@/lib/usageDb.js";
|
||||
<<<<<<< HEAD
|
||||
import { extractUsage, hasValidUsage, estimateUsage, addBufferToUsage, filterUsageForFormat, COLORS } from "./usageTracking.js";
|
||||
import { saveUsageStats } from "../handlers/chatCore/requestDetail.js";
|
||||
=======
|
||||
import { extractUsage, mergeUsage, hasValidUsage, estimateUsage, logUsage, addBufferToUsage, filterUsageForFormat, COLORS } from "./usageTracking.js";
|
||||
>>>>>>> 7f436e2792be4fa5a4d1c4d6b8e9bc85eaaa6a3d
|
||||
import { parseSSELine, hasValuableContent, fixInvalidId, formatSSE } from "./streamHelpers.js";
|
||||
import { getOpenAIResponsesEventName, isOpenAIResponsesTerminalEvent, formatIncompleteOpenAIResponsesStreamFailure } from "./responsesStreamHelpers.js";
|
||||
import { dbg, isDebugEnabled } from "./debugLog.js";
|
||||
@@ -131,6 +135,20 @@ export function createSSEStream(options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
// Strip empty tool_calls arrays that break AI SDK reasoning tracking.
|
||||
// Some providers (e.g. CodeBuddy CN) include `"tool_calls": []` in
|
||||
// every streaming delta. @ai-sdk/openai-compatible checks
|
||||
// `delta.tool_calls != null` — an empty array passes this check,
|
||||
// causing premature `reasoning-end` on every chunk.
|
||||
if (parsed?.choices) {
|
||||
for (const choice of parsed.choices) {
|
||||
if (choice.delta?.tool_calls && Array.isArray(choice.delta.tool_calls) && choice.delta.tool_calls.length === 0) {
|
||||
delete choice.delta.tool_calls;
|
||||
fieldsInjected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasValuableContent(parsed, FORMATS.OPENAI)) {
|
||||
continue;
|
||||
}
|
||||
@@ -149,7 +167,7 @@ export function createSSEStream(options = {}) {
|
||||
|
||||
const extracted = extractUsage(parsed);
|
||||
if (extracted) {
|
||||
usage = extracted;
|
||||
usage = mergeUsage(usage, extracted);
|
||||
}
|
||||
|
||||
const isFinishChunk = parsed.choices?.[0]?.finish_reason;
|
||||
@@ -218,9 +236,11 @@ export function createSSEStream(options = {}) {
|
||||
sseEmittedCount++;
|
||||
}
|
||||
|
||||
// [DONE] not emitted in translate mode — some clients' SSE decoders
|
||||
// fail to parse the OpenAI sentinel on Claude-format translated streams.
|
||||
// message_stop already signals end-of-response; stream close handles it.
|
||||
if (keepsOpenAIResponsesFormat && !streamDoneSent) {
|
||||
const doneOutput = "data: [DONE]\n\n";
|
||||
reqLogger?.appendConvertedChunk?.(doneOutput);
|
||||
controller.enqueue(sharedEncoder.encode(doneOutput));
|
||||
}
|
||||
streamDoneSent = true;
|
||||
if (keepsOpenAIResponsesFormat) openAIResponsesDoneSent = true;
|
||||
continue;
|
||||
@@ -265,7 +285,7 @@ export function createSSEStream(options = {}) {
|
||||
|
||||
// Extract usage
|
||||
const extracted = extractUsage(parsed);
|
||||
if (extracted) state.usage = extracted; // Keep original usage for logging
|
||||
if (extracted) state.usage = mergeUsage(state.usage, extracted); // Keep original usage for logging
|
||||
|
||||
// Responses same-format passthrough: re-emit with original event framing
|
||||
if (keepsOpenAIResponsesFormat && openAIResponsesEventName) {
|
||||
@@ -351,7 +371,9 @@ export function createSSEStream(options = {}) {
|
||||
// Some clients (e.g. OpenClaw) expect the OpenAI-style sentinel:
|
||||
// data: [DONE]\n\n
|
||||
// Without it they can hang until timeout and trigger failover.
|
||||
if (!streamDoneSent) {
|
||||
// Gemini-family clients (Antigravity, Vertex, Gemini) reject this sentinel with 400 syntax errors.
|
||||
const isGeminiFamily = provider === "antigravity" || provider === "gemini" || provider === "vertex";
|
||||
if (!streamDoneSent && !isGeminiFamily) {
|
||||
const doneOutput = "data: [DONE]\n\n";
|
||||
reqLogger?.appendConvertedChunk?.(doneOutput);
|
||||
controller.enqueue(sharedEncoder.encode(doneOutput));
|
||||
@@ -416,8 +438,13 @@ export function createSSEStream(options = {}) {
|
||||
openAIResponsesTerminalSeen = true;
|
||||
}
|
||||
|
||||
// [DONE] not emitted in translate mode — see comment above.
|
||||
// Passthrough mode still emits it for standard OpenAI clients.
|
||||
if (keepsOpenAIResponsesFormat && !openAIResponsesDoneSent && !streamDoneSent) {
|
||||
const doneOutput = "data: [DONE]\n\n";
|
||||
reqLogger?.appendConvertedChunk?.(doneOutput);
|
||||
controller.enqueue(sharedEncoder.encode(doneOutput));
|
||||
openAIResponsesDoneSent = true;
|
||||
streamDoneSent = true;
|
||||
}
|
||||
|
||||
if (!hasValidUsage(state?.usage) && totalContentLength > 0) {
|
||||
state.usage = estimateUsage(body, totalContentLength, sourceFormat);
|
||||
|
||||
Reference in New Issue
Block a user