fix(#8171): map DeepSeek prompt_cache_hit_tokens into prompt_tokens_details.cached_tokens

DeepSeek native API returns cache stats in flat top-level fields
(prompt_cache_hit_tokens / prompt_cache_miss_tokens) instead of
the standard prompt_tokens_details.cached_tokens. The usage
sanitizer (sanitizeUsage / sanitizeResponsesUsage) was stripping
these non-standard fields, so clients never received real cache
hit counts even when the upstream served cached responses.

Changes:
- sanitizeUsage(): map prompt_cache_hit_tokens into
  prompt_tokens_details.cached_tokens when the latter is unset
- sanitizeResponsesUsage(): same mapping for input_tokens_details
- filterUsageForFormat(): add prompt_cache_hit_tokens and
  prompt_cache_miss_tokens to the default format allow list
  so they survive field-level filtering
This commit is contained in:
ikelvingo
2026-07-25 14:57:10 +08:00
parent 81ade9e122
commit 595c0dba04
2 changed files with 26 additions and 0 deletions

View File

@@ -483,6 +483,19 @@ function sanitizeUsage(usage: unknown): unknown {
}
}
// DeepSeek native API uses flat prompt_cache_hit_tokens (NOT
// prompt_tokens_details.cached_tokens). Map it into prompt_tokens_details
// so clients see the real cache hit count (#8171).
if (
usageRecord.prompt_cache_hit_tokens !== undefined &&
(!sanitized.prompt_tokens_details ||
!(sanitized.prompt_tokens_details as Record<string, unknown>).cached_tokens)
) {
const details = (sanitized.prompt_tokens_details as Record<string, unknown>) ?? {};
details.cached_tokens = usageRecord.prompt_cache_hit_tokens;
sanitized.prompt_tokens_details = details;
}
// Ensure required fields
const promptTokens = toNumber(sanitized.prompt_tokens) ?? 0;
const completionTokens = toNumber(sanitized.completion_tokens) ?? 0;
@@ -520,6 +533,17 @@ function sanitizeResponsesUsage(usage: unknown): unknown {
normalized.output_tokens_details = normalized.completion_tokens_details;
}
// DeepSeek native API: map flat prompt_cache_hit_tokens into input_tokens_details
if (
normalized.prompt_cache_hit_tokens !== undefined &&
!normalized.input_tokens_details?.cached_tokens
) {
normalized.input_tokens_details = {
...(normalized.input_tokens_details as Record<string, unknown> || {}),
cached_tokens: normalized.prompt_cache_hit_tokens,
};
}
const inputDetails = toRecord(normalized.input_tokens_details) || {};
const cachedTokens = normalized.cached_tokens ?? normalized.cache_read_input_tokens;
if (cachedTokens !== undefined && inputDetails.cached_tokens === undefined) {

View File

@@ -235,6 +235,8 @@ export function filterUsageForFormat(usage, targetFormat) {
"reasoning_tokens",
"prompt_tokens_details",
"completion_tokens_details",
"prompt_cache_hit_tokens",
"prompt_cache_miss_tokens",
"estimated",
],
};