diff --git a/packages/core/src/core/openaiContentGenerator/converter.test.ts b/packages/core/src/core/openaiContentGenerator/converter.test.ts index 026fa38b2df..b7ac55581fc 100644 --- a/packages/core/src/core/openaiContentGenerator/converter.test.ts +++ b/packages/core/src/core/openaiContentGenerator/converter.test.ts @@ -2691,6 +2691,36 @@ describe('OpenAIContentConverter', () => { expect(response.candidates).toEqual([]); }); + + it('keeps the estimated prompt/completion split summing to total tokens', () => { + // When a provider reports only total_tokens, the 70/30 estimate must + // still add back up to the total instead of rounding each half on its + // own (5 would otherwise become 4 + 2 = 6). + const response = converter.convertOpenAIResponseToGemini( + { + object: 'chat.completion', + id: 'chatcmpl-usage', + created: 123, + model: 'test-model', + choices: [ + { + index: 0, + message: { role: 'assistant', content: 'hi' }, + finish_reason: 'stop', + logprobs: null, + }, + ], + usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 5 }, + } as unknown as OpenAI.Chat.ChatCompletion, + requestContext, + ); + + const usage = response.usageMetadata; + expect(usage?.totalTokenCount).toBe(5); + expect( + (usage?.promptTokenCount ?? 0) + (usage?.candidatesTokenCount ?? 0), + ).toBe(5); + }); }); describe('OpenAI -> Gemini reasoning content', () => { diff --git a/packages/core/src/core/openaiContentGenerator/converter.ts b/packages/core/src/core/openaiContentGenerator/converter.ts index 88d17d99cd4..a9f3ae53a5a 100644 --- a/packages/core/src/core/openaiContentGenerator/converter.ts +++ b/packages/core/src/core/openaiContentGenerator/converter.ts @@ -1182,9 +1182,11 @@ export function convertOpenAIResponseToGemini( let finalCompletionTokens = completionTokens; if (totalTokens > 0 && promptTokens === 0 && completionTokens === 0) { - // Estimate: assume 70% input, 30% output + // Estimate: assume 70% input, 30% output. Derive completion from the + // remainder so the two halves always add back up to totalTokens rather + // than rounding each independently (e.g. 5 would give 4 + 2 = 6). finalPromptTokens = Math.round(totalTokens * 0.7); - finalCompletionTokens = Math.round(totalTokens * 0.3); + finalCompletionTokens = totalTokens - finalPromptTokens; } response.usageMetadata = { @@ -1379,9 +1381,11 @@ export function convertOpenAIChunkToGemini( let finalCompletionTokens = completionTokens; if (totalTokens > 0 && promptTokens === 0 && completionTokens === 0) { - // Estimate: assume 70% input, 30% output + // Estimate: assume 70% input, 30% output. Derive completion from the + // remainder so the two halves always add back up to totalTokens rather + // than rounding each independently (e.g. 5 would give 4 + 2 = 6). finalPromptTokens = Math.round(totalTokens * 0.7); - finalCompletionTokens = Math.round(totalTokens * 0.3); + finalCompletionTokens = totalTokens - finalPromptTokens; } response.usageMetadata = {