( usage: OpenAI.Chat.Completions.ChatCompletion['usage'] | undefined | null, )
| 13 | * usage object, so callers omit the field rather than fabricating zeroed totals. |
| 14 | */ |
| 15 | export function buildChatCompletionsUsage( |
| 16 | usage: OpenAI.Chat.Completions.ChatCompletion['usage'] | undefined | null, |
| 17 | ): TokenUsage | undefined { |
| 18 | if (!usage) return undefined |
| 19 | |
| 20 | const result = buildBaseUsage({ |
| 21 | promptTokens: usage.prompt_tokens || 0, |
| 22 | completionTokens: usage.completion_tokens || 0, |
| 23 | totalTokens: usage.total_tokens || 0, |
| 24 | }) |
| 25 | |
| 26 | const completionDetails = usage.completion_tokens_details |
| 27 | const completionTokensDetails = { |
| 28 | ...(completionDetails?.reasoning_tokens |
| 29 | ? { reasoningTokens: completionDetails.reasoning_tokens } |
| 30 | : {}), |
| 31 | ...(completionDetails?.audio_tokens |
| 32 | ? { audioTokens: completionDetails.audio_tokens } |
| 33 | : {}), |
| 34 | } |
| 35 | |
| 36 | const promptDetails = usage.prompt_tokens_details |
| 37 | const promptTokensDetails = { |
| 38 | ...(promptDetails?.cached_tokens |
| 39 | ? { cachedTokens: promptDetails.cached_tokens } |
| 40 | : {}), |
| 41 | ...(promptDetails?.audio_tokens |
| 42 | ? { audioTokens: promptDetails.audio_tokens } |
| 43 | : {}), |
| 44 | } |
| 45 | |
| 46 | if (Object.keys(completionTokensDetails).length > 0) { |
| 47 | result.completionTokensDetails = completionTokensDetails |
| 48 | } |
| 49 | if (Object.keys(promptTokensDetails).length > 0) { |
| 50 | result.promptTokensDetails = promptTokensDetails |
| 51 | } |
| 52 | |
| 53 | // Predicted Outputs accepted/rejected counts have no canonical TokenUsage |
| 54 | // slot but are still billed (rejected tokens included), so surface them under |
| 55 | // providerUsageDetails — matching how the OpenRouter adapter exposes them. |
| 56 | const providerUsageDetails = { |
| 57 | ...(completionDetails?.accepted_prediction_tokens |
| 58 | ? { |
| 59 | acceptedPredictionTokens: |
| 60 | completionDetails.accepted_prediction_tokens, |
| 61 | } |
| 62 | : {}), |
| 63 | ...(completionDetails?.rejected_prediction_tokens |
| 64 | ? { |
| 65 | rejectedPredictionTokens: |
| 66 | completionDetails.rejected_prediction_tokens, |
| 67 | } |
| 68 | : {}), |
| 69 | } |
| 70 | if (Object.keys(providerUsageDetails).length > 0) { |
| 71 | result.providerUsageDetails = providerUsageDetails |
| 72 | } |
no test coverage detected