mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-03 19:12:04 +08:00
fix: inject stream_options.include_usage for Codex Chat streaming
Responses-to-Chat conversion did not set stream_options.include_usage, so OpenAI-compatible upstreams (kimi/MiniMax, etc.) omitted the trailing usage chunk in streaming mode. This caused token/cost/cache stats to be recorded as zero for third-party streaming requests routed through the Codex Chat path. Inject include_usage when the request is streaming, merging into any client-provided stream_options instead of overwriting it. Add tests for the streaming, non-streaming, and merge cases.
This commit is contained in:
@@ -102,6 +102,26 @@ pub fn responses_to_chat_completions(body: Value) -> Result<Value, ProxyError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenAI 兼容上游在流式下默认不在 SSE 里返回 usage,必须显式声明
|
||||||
|
// include_usage 才会在末尾吐 usage chunk。Codex CLI 用 Responses 协议、
|
||||||
|
// 自身不带 stream_options,缺这一注入会导致 kimi/MiniMax 等第三方流式请求的
|
||||||
|
// token/成本/缓存命中率全部漏记(input/output/cache 全为 0)。
|
||||||
|
let is_stream = result
|
||||||
|
.get("stream")
|
||||||
|
.and_then(|v| v.as_bool())
|
||||||
|
.unwrap_or(false);
|
||||||
|
if is_stream {
|
||||||
|
match result.get_mut("stream_options") {
|
||||||
|
// 保留客户端可能透传的其它 stream_options 字段,仅补 include_usage。
|
||||||
|
Some(Value::Object(opts)) => {
|
||||||
|
opts.insert("include_usage".to_string(), json!(true));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
result["stream_options"] = json!({ "include_usage": true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -988,6 +1008,48 @@ pub fn chat_error_to_response_error(body: Option<&Value>) -> Value {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn responses_request_with_stream_injects_include_usage() {
|
||||||
|
let input = json!({
|
||||||
|
"model": "kimi-k2.6",
|
||||||
|
"input": [{"role": "user", "content": [{"type": "input_text", "text": "hi"}]}],
|
||||||
|
"stream": true
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = responses_to_chat_completions(input).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result["stream"], true);
|
||||||
|
assert_eq!(result["stream_options"]["include_usage"], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn responses_request_without_stream_omits_stream_options() {
|
||||||
|
let input = json!({
|
||||||
|
"model": "kimi-k2.6",
|
||||||
|
"input": [{"role": "user", "content": [{"type": "input_text", "text": "hi"}]}]
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = responses_to_chat_completions(input).unwrap();
|
||||||
|
|
||||||
|
assert!(result.get("stream_options").is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn responses_request_merges_include_usage_into_existing_stream_options() {
|
||||||
|
let input = json!({
|
||||||
|
"model": "kimi-k2.6",
|
||||||
|
"input": [{"role": "user", "content": [{"type": "input_text", "text": "hi"}]}],
|
||||||
|
"stream": true,
|
||||||
|
"stream_options": {"continuous_usage_stats": true}
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = responses_to_chat_completions(input).unwrap();
|
||||||
|
|
||||||
|
// 既补上 include_usage,又保留客户端原有的 stream_options 字段。
|
||||||
|
assert_eq!(result["stream_options"]["include_usage"], true);
|
||||||
|
assert_eq!(result["stream_options"]["continuous_usage_stats"], true);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn responses_request_to_chat_maps_messages_tools_and_limits() {
|
fn responses_request_to_chat_maps_messages_tools_and_limits() {
|
||||||
let input = json!({
|
let input = json!({
|
||||||
|
|||||||
Reference in New Issue
Block a user