refactor(open-sse): extract buildUsage helper, dedup token-details (B2)
Add helpers/usageHelper.js for conditional prompt/completion token details. Apply to gemini/codex/claude response translators; keep each provider's token math intact. No behavior change; golden + gate: no regression. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
14
open-sse/translator/helpers/usageHelper.js
Normal file
14
open-sse/translator/helpers/usageHelper.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// Build OpenAI usage object. Caller computes prompt/completion/total (provider math).
|
||||||
|
// Optional details added only when > 0 (matches existing claude/gemini/codex behavior).
|
||||||
|
export function buildUsage({ promptTokens, completionTokens, totalTokens, cachedTokens = 0, cacheCreationTokens = 0, reasoningTokens = 0 }) {
|
||||||
|
const usage = { prompt_tokens: promptTokens, completion_tokens: completionTokens, total_tokens: totalTokens };
|
||||||
|
if (cachedTokens > 0 || cacheCreationTokens > 0) {
|
||||||
|
usage.prompt_tokens_details = {};
|
||||||
|
if (cachedTokens > 0) usage.prompt_tokens_details.cached_tokens = cachedTokens;
|
||||||
|
if (cacheCreationTokens > 0) usage.prompt_tokens_details.cache_creation_tokens = cacheCreationTokens;
|
||||||
|
}
|
||||||
|
if (reasoningTokens > 0) {
|
||||||
|
usage.completion_tokens_details = { reasoning_tokens: reasoningTokens };
|
||||||
|
}
|
||||||
|
return usage;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { register } from "../index.js";
|
import { register } from "../index.js";
|
||||||
import { FORMATS } from "../formats.js";
|
import { FORMATS } from "../formats.js";
|
||||||
import { buildChunk } from "../helpers/chunkBuilder.js";
|
import { buildChunk } from "../helpers/chunkBuilder.js";
|
||||||
|
import { buildUsage } from "../helpers/usageHelper.js";
|
||||||
|
|
||||||
// Create OpenAI chunk helper
|
// Create OpenAI chunk helper
|
||||||
function createChunk(state, delta, finishReason = null) {
|
function createChunk(state, delta, finishReason = null) {
|
||||||
@@ -127,19 +128,13 @@ export function claudeToOpenAIResponse(chunk, state) {
|
|||||||
const finalChunk = createChunk(state, {}, state.finishReason);
|
const finalChunk = createChunk(state, {}, state.finishReason);
|
||||||
|
|
||||||
if (state.usage) {
|
if (state.usage) {
|
||||||
finalChunk.usage = {
|
finalChunk.usage = buildUsage({
|
||||||
prompt_tokens: state.usage.prompt_tokens,
|
promptTokens: state.usage.prompt_tokens,
|
||||||
completion_tokens: state.usage.completion_tokens,
|
completionTokens: state.usage.completion_tokens,
|
||||||
total_tokens: state.usage.total_tokens
|
totalTokens: state.usage.total_tokens,
|
||||||
};
|
cachedTokens: state.usage.cache_read_input_tokens || 0,
|
||||||
|
cacheCreationTokens: state.usage.cache_creation_input_tokens || 0
|
||||||
const cacheRead = state.usage.cache_read_input_tokens;
|
});
|
||||||
const cacheCreate = state.usage.cache_creation_input_tokens;
|
|
||||||
if (cacheRead > 0 || cacheCreate > 0) {
|
|
||||||
finalChunk.usage.prompt_tokens_details = {};
|
|
||||||
if (cacheRead > 0) finalChunk.usage.prompt_tokens_details.cached_tokens = cacheRead;
|
|
||||||
if (cacheCreate > 0) finalChunk.usage.prompt_tokens_details.cache_creation_tokens = cacheCreate;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
results.push(finalChunk);
|
results.push(finalChunk);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { register } from "../index.js";
|
import { register } from "../index.js";
|
||||||
import { FORMATS } from "../formats.js";
|
import { FORMATS } from "../formats.js";
|
||||||
import { buildChunk } from "../helpers/chunkBuilder.js";
|
import { buildChunk } from "../helpers/chunkBuilder.js";
|
||||||
|
import { buildUsage } from "../helpers/usageHelper.js";
|
||||||
|
|
||||||
// Build chunk meta for current gemini state
|
// Build chunk meta for current gemini state
|
||||||
function chunkMeta(state) {
|
function chunkMeta(state) {
|
||||||
@@ -144,25 +145,7 @@ export function geminiToOpenAIResponse(chunk, state) {
|
|||||||
// completion_tokens = candidatesTokenCount + thoughtsTokenCount (match Go code)
|
// completion_tokens = candidatesTokenCount + thoughtsTokenCount (match Go code)
|
||||||
const completionTokens = candidatesTokens + thoughtsTokens;
|
const completionTokens = candidatesTokens + thoughtsTokens;
|
||||||
|
|
||||||
state.usage = {
|
state.usage = buildUsage({ promptTokens, completionTokens, totalTokens, cachedTokens, reasoningTokens: thoughtsTokens });
|
||||||
prompt_tokens: promptTokens,
|
|
||||||
completion_tokens: completionTokens,
|
|
||||||
total_tokens: totalTokens
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add prompt_tokens_details if cached tokens exist
|
|
||||||
if (cachedTokens > 0) {
|
|
||||||
state.usage.prompt_tokens_details = {
|
|
||||||
cached_tokens: cachedTokens
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add completion_tokens_details if reasoning tokens exist
|
|
||||||
if (thoughtsTokens > 0) {
|
|
||||||
state.usage.completion_tokens_details = {
|
|
||||||
reasoning_tokens: thoughtsTokens
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finish reason - include usage in final chunk
|
// Finish reason - include usage in final chunk
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import { register } from "../index.js";
|
import { register } from "../index.js";
|
||||||
import { FORMATS } from "../formats.js";
|
import { FORMATS } from "../formats.js";
|
||||||
import { buildChunk } from "../helpers/chunkBuilder.js";
|
import { buildChunk } from "../helpers/chunkBuilder.js";
|
||||||
|
import { buildUsage } from "../helpers/usageHelper.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translate OpenAI chunk to Responses API events
|
* Translate OpenAI chunk to Responses API events
|
||||||
@@ -466,18 +467,7 @@ export function openaiResponsesToOpenAIResponse(chunk, state) {
|
|||||||
// Cache info is in input_tokens_details.cached_tokens
|
// Cache info is in input_tokens_details.cached_tokens
|
||||||
const cacheReadTokens = responseUsage.input_tokens_details?.cached_tokens || responseUsage.cache_read_input_tokens || 0;
|
const cacheReadTokens = responseUsage.input_tokens_details?.cached_tokens || responseUsage.cache_read_input_tokens || 0;
|
||||||
|
|
||||||
state.usage = {
|
state.usage = buildUsage({ promptTokens: inputTokens, completionTokens: outputTokens, totalTokens: inputTokens + outputTokens, cachedTokens: cacheReadTokens });
|
||||||
prompt_tokens: inputTokens,
|
|
||||||
completion_tokens: outputTokens,
|
|
||||||
total_tokens: inputTokens + outputTokens
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add prompt_tokens_details if cache tokens exist
|
|
||||||
if (cacheReadTokens > 0) {
|
|
||||||
state.usage.prompt_tokens_details = {
|
|
||||||
cached_tokens: cacheReadTokens
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.finishReasonSent) {
|
if (!state.finishReasonSent) {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user