fix(usage): dedupe streaming request-details log entries

handleStreamingResponse and buildOnStreamComplete each generated their
own streamDetailId for what should be one logical record — the
placeholder row (0 tokens) and the final row (real usage) never shared
an id, so the DB's ON CONFLICT(id) upsert never merged them, leaving a
permanent 0-token stub for every streaming request.

Share the id from buildOnStreamComplete with handleStreamingResponse
so both writes hit the same row.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Qin Li
2026-07-03 15:07:20 +07:00
committed by decolua
parent 5cc4f222f8
commit 960f8a0379
2 changed files with 3 additions and 4 deletions

View File

@@ -326,8 +326,8 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred
} }
// Streaming response // Streaming response
const { onStreamComplete } = buildOnStreamComplete({ ...sharedCtx }); const { onStreamComplete, streamDetailId } = buildOnStreamComplete({ ...sharedCtx });
return handleStreamingResponse({ ...sharedCtx, providerResponse, sourceFormat, targetFormat, userAgent, reqLogger, toolNameMap, streamController, onStreamComplete }); return handleStreamingResponse({ ...sharedCtx, providerResponse, sourceFormat, targetFormat, userAgent, reqLogger, toolNameMap, streamController, onStreamComplete, streamDetailId });
} }
export function isTokenExpiringSoon(expiresAt, bufferMs = 5 * 60 * 1000) { export function isTokenExpiringSoon(expiresAt, bufferMs = 5 * 60 * 1000) {

View File

@@ -43,7 +43,7 @@ function buildTransformStream({ provider, sourceFormat, targetFormat, userAgent,
/** /**
* Handle streaming response — pipe provider SSE through transform stream to client. * Handle streaming response — pipe provider SSE through transform stream to client.
*/ */
export async function handleStreamingResponse({ providerResponse, provider, model, sourceFormat, targetFormat, userAgent, body, stream, translatedBody, finalBody, requestStartTime, connectionId, apiKey, clientRawRequest, onRequestSuccess, reqLogger, toolNameMap, streamController, onStreamComplete }) { export async function handleStreamingResponse({ providerResponse, provider, model, sourceFormat, targetFormat, userAgent, body, stream, translatedBody, finalBody, requestStartTime, connectionId, apiKey, clientRawRequest, onRequestSuccess, reqLogger, toolNameMap, streamController, onStreamComplete, streamDetailId }) {
if (onRequestSuccess) { if (onRequestSuccess) {
Promise.resolve() Promise.resolve()
.then(onRequestSuccess) .then(onRequestSuccess)
@@ -86,7 +86,6 @@ export async function handleStreamingResponse({ providerResponse, provider, mode
const stallTimeoutMs = PROVIDERS[provider]?.stallTimeoutMs || STREAM_STALL_TIMEOUT_MS; const stallTimeoutMs = PROVIDERS[provider]?.stallTimeoutMs || STREAM_STALL_TIMEOUT_MS;
const transformedBody = pipeWithDisconnect(providerResponse, transformStream, streamController, onAbortTerminal, stallTimeoutMs); const transformedBody = pipeWithDisconnect(providerResponse, transformStream, streamController, onAbortTerminal, stallTimeoutMs);
const streamDetailId = `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
saveRequestDetail(buildRequestDetail({ saveRequestDetail(buildRequestDetail({
provider, model, connectionId, provider, model, connectionId,
latency: { ttft: 0, total: Date.now() - requestStartTime }, latency: { ttft: 0, total: Date.now() - requestStartTime },