mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-10-02 23:24:10 +00:00
fix(adapters): pass through cache tokens in the remaining 8 adapters
Live testing proved both sensenova and zen DO return cache fields: - zen laguna-s-2.1-free: usage.prompt_tokens_details.cached_tokens = 32 (real hit), plus cache_write_tokens/audio_tokens - sensenova glm-5.2: prompt_tokens_details.cached_tokens present (0 on short prompts) The previous round only patched deepseek/openai/anthropic/gemini.lua; sensenova/opencode (localzen!) and the other adapters still dropped them. - sensenova/opencode/groq/mistral/github/kimicode: stream + response cache passthrough (same pattern as openai.lua) - agentrouter: response passthrough + NEW stream usage forwarding (it previously dropped the terminal usage-only chunk entirely) - ollama skipped intentionally: its native API has no cache fields Verified end-to-end through the gateway: localzen/laguna-s-2.1-free now returns prompt_tokens_details.cached_tokens=32 to clients, and the request record carries cache_hit_tokens (both chat and stream paths).
This commit is contained in:
@ -52,6 +52,18 @@ function adapter.transform_response(raw_body)
|
|||||||
unified.token_usage.prompt = resp.usage.prompt_tokens or 0
|
unified.token_usage.prompt = resp.usage.prompt_tokens or 0
|
||||||
unified.token_usage.completion = resp.usage.completion_tokens or 0
|
unified.token_usage.completion = resp.usage.completion_tokens or 0
|
||||||
unified.token_usage.total = resp.usage.total_tokens or 0
|
unified.token_usage.total = resp.usage.total_tokens or 0
|
||||||
|
local hit = 0
|
||||||
|
if type(resp.usage.prompt_tokens_details) == "table" and (resp.usage.prompt_tokens_details.cached_tokens or 0) > 0 then
|
||||||
|
hit = resp.usage.prompt_tokens_details.cached_tokens
|
||||||
|
unified.token_usage.prompt_tokens_details = { cached_tokens = hit }
|
||||||
|
end
|
||||||
|
if (resp.usage.prompt_cache_hit_tokens or 0) > 0 then
|
||||||
|
unified.token_usage.prompt_cache_hit_tokens = resp.usage.prompt_cache_hit_tokens
|
||||||
|
unified.token_usage.prompt_cache_miss_tokens = resp.usage.prompt_cache_miss_tokens or 0
|
||||||
|
if hit == 0 then
|
||||||
|
unified.token_usage.prompt_tokens_details = { cached_tokens = resp.usage.prompt_cache_hit_tokens }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(resp.choices) == "table" and #resp.choices > 0 then
|
if type(resp.choices) == "table" and #resp.choices > 0 then
|
||||||
@ -86,7 +98,30 @@ function adapter.transform_stream_chunk(raw_chunk)
|
|||||||
local ok, chunk = pcall(json.decode, raw_chunk)
|
local ok, chunk = pcall(json.decode, raw_chunk)
|
||||||
if not ok then return "" end
|
if not ok then return "" end
|
||||||
|
|
||||||
if not chunk.choices or #chunk.choices == 0 then return "" end
|
-- usage-only terminal chunk (empty choices + usage): forward it so the
|
||||||
|
-- gateway emits exact token counts (and cache fields when present).
|
||||||
|
local uses = nil
|
||||||
|
if type(chunk.usage) == "table" then
|
||||||
|
uses = {
|
||||||
|
prompt = chunk.usage.prompt_tokens or chunk.usage.prompt or 0,
|
||||||
|
completion = chunk.usage.completion_tokens or chunk.usage.completion or 0,
|
||||||
|
total = chunk.usage.total_tokens or chunk.usage.total or 0,
|
||||||
|
}
|
||||||
|
if type(chunk.usage.prompt_tokens_details) == "table" and (chunk.usage.prompt_tokens_details.cached_tokens or 0) > 0 then
|
||||||
|
uses.prompt_tokens_details = { cached_tokens = chunk.usage.prompt_tokens_details.cached_tokens }
|
||||||
|
elseif (chunk.usage.prompt_cache_hit_tokens or 0) > 0 then
|
||||||
|
uses.prompt_cache_hit_tokens = chunk.usage.prompt_cache_hit_tokens
|
||||||
|
uses.prompt_cache_miss_tokens = chunk.usage.prompt_cache_miss_tokens or 0
|
||||||
|
uses.prompt_tokens_details = { cached_tokens = chunk.usage.prompt_cache_hit_tokens }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not chunk.choices or #chunk.choices == 0 then
|
||||||
|
if uses ~= nil then
|
||||||
|
return json.encode({ usage = uses, done = false })
|
||||||
|
end
|
||||||
|
return ""
|
||||||
|
end
|
||||||
local delta = chunk.choices[1].delta or {}
|
local delta = chunk.choices[1].delta or {}
|
||||||
local fr = chunk.choices[1].finish_reason
|
local fr = chunk.choices[1].finish_reason
|
||||||
|
|
||||||
@ -105,6 +140,9 @@ function adapter.transform_stream_chunk(raw_chunk)
|
|||||||
if delta.tool_calls then
|
if delta.tool_calls then
|
||||||
unified.tool_calls = delta.tool_calls
|
unified.tool_calls = delta.tool_calls
|
||||||
end
|
end
|
||||||
|
if uses ~= nil then
|
||||||
|
unified.usage = uses
|
||||||
|
end
|
||||||
return json.encode(unified)
|
return json.encode(unified)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,18 @@ function adapter.transform_response(raw_body)
|
|||||||
unified.token_usage.prompt = resp.usage.prompt_tokens or 0
|
unified.token_usage.prompt = resp.usage.prompt_tokens or 0
|
||||||
unified.token_usage.completion = resp.usage.completion_tokens or 0
|
unified.token_usage.completion = resp.usage.completion_tokens or 0
|
||||||
unified.token_usage.total = resp.usage.total_tokens or 0
|
unified.token_usage.total = resp.usage.total_tokens or 0
|
||||||
|
local hit = 0
|
||||||
|
if type(resp.usage.prompt_tokens_details) == "table" and (resp.usage.prompt_tokens_details.cached_tokens or 0) > 0 then
|
||||||
|
hit = resp.usage.prompt_tokens_details.cached_tokens
|
||||||
|
unified.token_usage.prompt_tokens_details = { cached_tokens = hit }
|
||||||
|
end
|
||||||
|
if (resp.usage.prompt_cache_hit_tokens or 0) > 0 then
|
||||||
|
unified.token_usage.prompt_cache_hit_tokens = resp.usage.prompt_cache_hit_tokens
|
||||||
|
unified.token_usage.prompt_cache_miss_tokens = resp.usage.prompt_cache_miss_tokens or 0
|
||||||
|
if hit == 0 then
|
||||||
|
unified.token_usage.prompt_tokens_details = { cached_tokens = resp.usage.prompt_cache_hit_tokens }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(resp.choices) == "table" and #resp.choices > 0 then
|
if type(resp.choices) == "table" and #resp.choices > 0 then
|
||||||
@ -74,6 +86,13 @@ function adapter.transform_stream_chunk(raw_chunk)
|
|||||||
completion = chunk.usage.completion_tokens or chunk.usage.completion or 0,
|
completion = chunk.usage.completion_tokens or chunk.usage.completion or 0,
|
||||||
total = chunk.usage.total_tokens or chunk.usage.total or 0,
|
total = chunk.usage.total_tokens or chunk.usage.total or 0,
|
||||||
}
|
}
|
||||||
|
if type(chunk.usage.prompt_tokens_details) == "table" and (chunk.usage.prompt_tokens_details.cached_tokens or 0) > 0 then
|
||||||
|
uses.prompt_tokens_details = { cached_tokens = chunk.usage.prompt_tokens_details.cached_tokens }
|
||||||
|
elseif (chunk.usage.prompt_cache_hit_tokens or 0) > 0 then
|
||||||
|
uses.prompt_cache_hit_tokens = chunk.usage.prompt_cache_hit_tokens
|
||||||
|
uses.prompt_cache_miss_tokens = chunk.usage.prompt_cache_miss_tokens or 0
|
||||||
|
uses.prompt_tokens_details = { cached_tokens = chunk.usage.prompt_cache_hit_tokens }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not chunk.choices or #chunk.choices == 0 then
|
if not chunk.choices or #chunk.choices == 0 then
|
||||||
|
|||||||
@ -32,6 +32,18 @@ function adapter.transform_response(raw_body)
|
|||||||
unified.token_usage.prompt = resp.usage.prompt_tokens or 0
|
unified.token_usage.prompt = resp.usage.prompt_tokens or 0
|
||||||
unified.token_usage.completion = resp.usage.completion_tokens or 0
|
unified.token_usage.completion = resp.usage.completion_tokens or 0
|
||||||
unified.token_usage.total = resp.usage.total_tokens or 0
|
unified.token_usage.total = resp.usage.total_tokens or 0
|
||||||
|
local hit = 0
|
||||||
|
if type(resp.usage.prompt_tokens_details) == "table" and (resp.usage.prompt_tokens_details.cached_tokens or 0) > 0 then
|
||||||
|
hit = resp.usage.prompt_tokens_details.cached_tokens
|
||||||
|
unified.token_usage.prompt_tokens_details = { cached_tokens = hit }
|
||||||
|
end
|
||||||
|
if (resp.usage.prompt_cache_hit_tokens or 0) > 0 then
|
||||||
|
unified.token_usage.prompt_cache_hit_tokens = resp.usage.prompt_cache_hit_tokens
|
||||||
|
unified.token_usage.prompt_cache_miss_tokens = resp.usage.prompt_cache_miss_tokens or 0
|
||||||
|
if hit == 0 then
|
||||||
|
unified.token_usage.prompt_tokens_details = { cached_tokens = resp.usage.prompt_cache_hit_tokens }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(resp.choices) == "table" and #resp.choices > 0 then
|
if type(resp.choices) == "table" and #resp.choices > 0 then
|
||||||
@ -73,6 +85,13 @@ function adapter.transform_stream_chunk(raw_chunk)
|
|||||||
completion = chunk.usage.completion_tokens or chunk.usage.completion or 0,
|
completion = chunk.usage.completion_tokens or chunk.usage.completion or 0,
|
||||||
total = chunk.usage.total_tokens or chunk.usage.total or 0,
|
total = chunk.usage.total_tokens or chunk.usage.total or 0,
|
||||||
}
|
}
|
||||||
|
if type(chunk.usage.prompt_tokens_details) == "table" and (chunk.usage.prompt_tokens_details.cached_tokens or 0) > 0 then
|
||||||
|
uses.prompt_tokens_details = { cached_tokens = chunk.usage.prompt_tokens_details.cached_tokens }
|
||||||
|
elseif (chunk.usage.prompt_cache_hit_tokens or 0) > 0 then
|
||||||
|
uses.prompt_cache_hit_tokens = chunk.usage.prompt_cache_hit_tokens
|
||||||
|
uses.prompt_cache_miss_tokens = chunk.usage.prompt_cache_miss_tokens or 0
|
||||||
|
uses.prompt_tokens_details = { cached_tokens = chunk.usage.prompt_cache_hit_tokens }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not chunk.choices or #chunk.choices == 0 then
|
if not chunk.choices or #chunk.choices == 0 then
|
||||||
|
|||||||
@ -64,6 +64,18 @@ function adapter.transform_response(raw_body)
|
|||||||
unified.token_usage.prompt = resp.usage.prompt_tokens or 0
|
unified.token_usage.prompt = resp.usage.prompt_tokens or 0
|
||||||
unified.token_usage.completion = resp.usage.completion_tokens or 0
|
unified.token_usage.completion = resp.usage.completion_tokens or 0
|
||||||
unified.token_usage.total = resp.usage.total_tokens or 0
|
unified.token_usage.total = resp.usage.total_tokens or 0
|
||||||
|
local hit = 0
|
||||||
|
if type(resp.usage.prompt_tokens_details) == "table" and (resp.usage.prompt_tokens_details.cached_tokens or 0) > 0 then
|
||||||
|
hit = resp.usage.prompt_tokens_details.cached_tokens
|
||||||
|
unified.token_usage.prompt_tokens_details = { cached_tokens = hit }
|
||||||
|
end
|
||||||
|
if (resp.usage.prompt_cache_hit_tokens or 0) > 0 then
|
||||||
|
unified.token_usage.prompt_cache_hit_tokens = resp.usage.prompt_cache_hit_tokens
|
||||||
|
unified.token_usage.prompt_cache_miss_tokens = resp.usage.prompt_cache_miss_tokens or 0
|
||||||
|
if hit == 0 then
|
||||||
|
unified.token_usage.prompt_tokens_details = { cached_tokens = resp.usage.prompt_cache_hit_tokens }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
if type(resp.choices) == "table" and #resp.choices > 0 then
|
if type(resp.choices) == "table" and #resp.choices > 0 then
|
||||||
local ch = resp.choices[1]
|
local ch = resp.choices[1]
|
||||||
@ -106,6 +118,13 @@ function adapter.transform_stream_chunk(raw_chunk)
|
|||||||
completion = chunk.usage.completion_tokens or chunk.usage.completion or 0,
|
completion = chunk.usage.completion_tokens or chunk.usage.completion or 0,
|
||||||
total = chunk.usage.total_tokens or chunk.usage.total or 0,
|
total = chunk.usage.total_tokens or chunk.usage.total or 0,
|
||||||
}
|
}
|
||||||
|
if type(chunk.usage.prompt_tokens_details) == "table" and (chunk.usage.prompt_tokens_details.cached_tokens or 0) > 0 then
|
||||||
|
uses.prompt_tokens_details = { cached_tokens = chunk.usage.prompt_tokens_details.cached_tokens }
|
||||||
|
elseif (chunk.usage.prompt_cache_hit_tokens or 0) > 0 then
|
||||||
|
uses.prompt_cache_hit_tokens = chunk.usage.prompt_cache_hit_tokens
|
||||||
|
uses.prompt_cache_miss_tokens = chunk.usage.prompt_cache_miss_tokens or 0
|
||||||
|
uses.prompt_tokens_details = { cached_tokens = chunk.usage.prompt_cache_hit_tokens }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not chunk.choices or #chunk.choices == 0 then
|
if not chunk.choices or #chunk.choices == 0 then
|
||||||
|
|||||||
@ -32,6 +32,18 @@ function adapter.transform_response(raw_body)
|
|||||||
unified.token_usage.prompt = resp.usage.prompt_tokens or 0
|
unified.token_usage.prompt = resp.usage.prompt_tokens or 0
|
||||||
unified.token_usage.completion = resp.usage.completion_tokens or 0
|
unified.token_usage.completion = resp.usage.completion_tokens or 0
|
||||||
unified.token_usage.total = resp.usage.total_tokens or 0
|
unified.token_usage.total = resp.usage.total_tokens or 0
|
||||||
|
local hit = 0
|
||||||
|
if type(resp.usage.prompt_tokens_details) == "table" and (resp.usage.prompt_tokens_details.cached_tokens or 0) > 0 then
|
||||||
|
hit = resp.usage.prompt_tokens_details.cached_tokens
|
||||||
|
unified.token_usage.prompt_tokens_details = { cached_tokens = hit }
|
||||||
|
end
|
||||||
|
if (resp.usage.prompt_cache_hit_tokens or 0) > 0 then
|
||||||
|
unified.token_usage.prompt_cache_hit_tokens = resp.usage.prompt_cache_hit_tokens
|
||||||
|
unified.token_usage.prompt_cache_miss_tokens = resp.usage.prompt_cache_miss_tokens or 0
|
||||||
|
if hit == 0 then
|
||||||
|
unified.token_usage.prompt_tokens_details = { cached_tokens = resp.usage.prompt_cache_hit_tokens }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(resp.choices) == "table" and #resp.choices > 0 then
|
if type(resp.choices) == "table" and #resp.choices > 0 then
|
||||||
@ -73,6 +85,13 @@ function adapter.transform_stream_chunk(raw_chunk)
|
|||||||
completion = chunk.usage.completion_tokens or chunk.usage.completion or 0,
|
completion = chunk.usage.completion_tokens or chunk.usage.completion or 0,
|
||||||
total = chunk.usage.total_tokens or chunk.usage.total or 0,
|
total = chunk.usage.total_tokens or chunk.usage.total or 0,
|
||||||
}
|
}
|
||||||
|
if type(chunk.usage.prompt_tokens_details) == "table" and (chunk.usage.prompt_tokens_details.cached_tokens or 0) > 0 then
|
||||||
|
uses.prompt_tokens_details = { cached_tokens = chunk.usage.prompt_tokens_details.cached_tokens }
|
||||||
|
elseif (chunk.usage.prompt_cache_hit_tokens or 0) > 0 then
|
||||||
|
uses.prompt_cache_hit_tokens = chunk.usage.prompt_cache_hit_tokens
|
||||||
|
uses.prompt_cache_miss_tokens = chunk.usage.prompt_cache_miss_tokens or 0
|
||||||
|
uses.prompt_tokens_details = { cached_tokens = chunk.usage.prompt_cache_hit_tokens }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not chunk.choices or #chunk.choices == 0 then
|
if not chunk.choices or #chunk.choices == 0 then
|
||||||
|
|||||||
@ -100,6 +100,18 @@ function adapter.transform_response(raw_body)
|
|||||||
unified.token_usage.prompt = resp.usage.prompt_tokens or 0
|
unified.token_usage.prompt = resp.usage.prompt_tokens or 0
|
||||||
unified.token_usage.completion = resp.usage.completion_tokens or 0
|
unified.token_usage.completion = resp.usage.completion_tokens or 0
|
||||||
unified.token_usage.total = resp.usage.total_tokens or 0
|
unified.token_usage.total = resp.usage.total_tokens or 0
|
||||||
|
local hit = 0
|
||||||
|
if type(resp.usage.prompt_tokens_details) == "table" and (resp.usage.prompt_tokens_details.cached_tokens or 0) > 0 then
|
||||||
|
hit = resp.usage.prompt_tokens_details.cached_tokens
|
||||||
|
unified.token_usage.prompt_tokens_details = { cached_tokens = hit }
|
||||||
|
end
|
||||||
|
if (resp.usage.prompt_cache_hit_tokens or 0) > 0 then
|
||||||
|
unified.token_usage.prompt_cache_hit_tokens = resp.usage.prompt_cache_hit_tokens
|
||||||
|
unified.token_usage.prompt_cache_miss_tokens = resp.usage.prompt_cache_miss_tokens or 0
|
||||||
|
if hit == 0 then
|
||||||
|
unified.token_usage.prompt_tokens_details = { cached_tokens = resp.usage.prompt_cache_hit_tokens }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(resp.choices) == "table" and #resp.choices > 0 then
|
if type(resp.choices) == "table" and #resp.choices > 0 then
|
||||||
@ -146,6 +158,13 @@ function adapter.transform_stream_chunk(raw_chunk)
|
|||||||
completion = chunk.usage.completion_tokens or chunk.usage.completion or 0,
|
completion = chunk.usage.completion_tokens or chunk.usage.completion or 0,
|
||||||
total = chunk.usage.total_tokens or chunk.usage.total or 0,
|
total = chunk.usage.total_tokens or chunk.usage.total or 0,
|
||||||
}
|
}
|
||||||
|
if type(chunk.usage.prompt_tokens_details) == "table" and (chunk.usage.prompt_tokens_details.cached_tokens or 0) > 0 then
|
||||||
|
uses.prompt_tokens_details = { cached_tokens = chunk.usage.prompt_tokens_details.cached_tokens }
|
||||||
|
elseif (chunk.usage.prompt_cache_hit_tokens or 0) > 0 then
|
||||||
|
uses.prompt_cache_hit_tokens = chunk.usage.prompt_cache_hit_tokens
|
||||||
|
uses.prompt_cache_miss_tokens = chunk.usage.prompt_cache_miss_tokens or 0
|
||||||
|
uses.prompt_tokens_details = { cached_tokens = chunk.usage.prompt_cache_hit_tokens }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not chunk.choices or #chunk.choices == 0 then
|
if not chunk.choices or #chunk.choices == 0 then
|
||||||
|
|||||||
@ -37,6 +37,18 @@ function adapter.transform_response(raw_body)
|
|||||||
unified.token_usage.prompt = resp.usage.prompt_tokens or 0
|
unified.token_usage.prompt = resp.usage.prompt_tokens or 0
|
||||||
unified.token_usage.completion = resp.usage.completion_tokens or 0
|
unified.token_usage.completion = resp.usage.completion_tokens or 0
|
||||||
unified.token_usage.total = resp.usage.total_tokens or 0
|
unified.token_usage.total = resp.usage.total_tokens or 0
|
||||||
|
local hit = 0
|
||||||
|
if type(resp.usage.prompt_tokens_details) == "table" and (resp.usage.prompt_tokens_details.cached_tokens or 0) > 0 then
|
||||||
|
hit = resp.usage.prompt_tokens_details.cached_tokens
|
||||||
|
unified.token_usage.prompt_tokens_details = { cached_tokens = hit }
|
||||||
|
end
|
||||||
|
if (resp.usage.prompt_cache_hit_tokens or 0) > 0 then
|
||||||
|
unified.token_usage.prompt_cache_hit_tokens = resp.usage.prompt_cache_hit_tokens
|
||||||
|
unified.token_usage.prompt_cache_miss_tokens = resp.usage.prompt_cache_miss_tokens or 0
|
||||||
|
if hit == 0 then
|
||||||
|
unified.token_usage.prompt_tokens_details = { cached_tokens = resp.usage.prompt_cache_hit_tokens }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(resp.choices) == "table" and #resp.choices > 0 then
|
if type(resp.choices) == "table" and #resp.choices > 0 then
|
||||||
@ -67,6 +79,13 @@ function adapter.transform_stream_chunk(raw_chunk)
|
|||||||
completion = chunk.usage.completion_tokens or chunk.usage.completion or 0,
|
completion = chunk.usage.completion_tokens or chunk.usage.completion or 0,
|
||||||
total = chunk.usage.total_tokens or chunk.usage.total or 0,
|
total = chunk.usage.total_tokens or chunk.usage.total or 0,
|
||||||
}
|
}
|
||||||
|
if type(chunk.usage.prompt_tokens_details) == "table" and (chunk.usage.prompt_tokens_details.cached_tokens or 0) > 0 then
|
||||||
|
uses.prompt_tokens_details = { cached_tokens = chunk.usage.prompt_tokens_details.cached_tokens }
|
||||||
|
elseif (chunk.usage.prompt_cache_hit_tokens or 0) > 0 then
|
||||||
|
uses.prompt_cache_hit_tokens = chunk.usage.prompt_cache_hit_tokens
|
||||||
|
uses.prompt_cache_miss_tokens = chunk.usage.prompt_cache_miss_tokens or 0
|
||||||
|
uses.prompt_tokens_details = { cached_tokens = chunk.usage.prompt_cache_hit_tokens }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not chunk.choices or #chunk.choices == 0 then
|
if not chunk.choices or #chunk.choices == 0 then
|
||||||
|
|||||||
Reference in New Issue
Block a user