fix(cost): count V4 reasoning tokens in usage output (#762)
This commit is contained in:
Generated
+14
-14
@@ -1080,7 +1080,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deepseek-agent"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
dependencies = [
|
||||
"deepseek-config",
|
||||
"serde",
|
||||
@@ -1088,7 +1088,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deepseek-app-server"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -1110,7 +1110,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deepseek-config"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deepseek-secrets",
|
||||
@@ -1122,7 +1122,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deepseek-core"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -1140,7 +1140,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deepseek-execpolicy"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deepseek-protocol",
|
||||
@@ -1149,7 +1149,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deepseek-hooks"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -1163,7 +1163,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deepseek-mcp"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -1172,7 +1172,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deepseek-protocol"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -1180,7 +1180,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deepseek-secrets"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"keyring",
|
||||
@@ -1193,7 +1193,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deepseek-state"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -1205,7 +1205,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deepseek-tools"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -1218,7 +1218,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deepseek-tui"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arboard",
|
||||
@@ -1277,7 +1277,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deepseek-tui-cli"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -1301,7 +1301,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deepseek-tui-core"
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ default-members = ["crates/cli", "crates/app-server", "crates/tui"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.8.13"
|
||||
version = "0.8.14"
|
||||
edition = "2024"
|
||||
# Rust 1.88 stabilized `let_chains` in `if`/`while` conditions, which the
|
||||
# codebase relies on extensively. Cargo enforces this so users on older
|
||||
|
||||
@@ -7,5 +7,5 @@ repository.workspace = true
|
||||
description = "Model/provider registry and fallback strategy for DeepSeek workspace architecture"
|
||||
|
||||
[dependencies]
|
||||
deepseek-config = { path = "../config", version = "0.8.13" }
|
||||
deepseek-config = { path = "../config", version = "0.8.14" }
|
||||
serde.workspace = true
|
||||
|
||||
@@ -10,15 +10,15 @@ description = "Codex-style app-server transport for DeepSeek workspace architect
|
||||
anyhow.workspace = true
|
||||
axum.workspace = true
|
||||
clap.workspace = true
|
||||
deepseek-agent = { path = "../agent", version = "0.8.13" }
|
||||
deepseek-config = { path = "../config", version = "0.8.13" }
|
||||
deepseek-core = { path = "../core", version = "0.8.13" }
|
||||
deepseek-execpolicy = { path = "../execpolicy", version = "0.8.13" }
|
||||
deepseek-hooks = { path = "../hooks", version = "0.8.13" }
|
||||
deepseek-mcp = { path = "../mcp", version = "0.8.13" }
|
||||
deepseek-protocol = { path = "../protocol", version = "0.8.13" }
|
||||
deepseek-state = { path = "../state", version = "0.8.13" }
|
||||
deepseek-tools = { path = "../tools", version = "0.8.13" }
|
||||
deepseek-agent = { path = "../agent", version = "0.8.14" }
|
||||
deepseek-config = { path = "../config", version = "0.8.14" }
|
||||
deepseek-core = { path = "../core", version = "0.8.14" }
|
||||
deepseek-execpolicy = { path = "../execpolicy", version = "0.8.14" }
|
||||
deepseek-hooks = { path = "../hooks", version = "0.8.14" }
|
||||
deepseek-mcp = { path = "../mcp", version = "0.8.14" }
|
||||
deepseek-protocol = { path = "../protocol", version = "0.8.14" }
|
||||
deepseek-state = { path = "../state", version = "0.8.14" }
|
||||
deepseek-tools = { path = "../tools", version = "0.8.14" }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
tokio.workspace = true
|
||||
|
||||
@@ -14,13 +14,13 @@ path = "src/main.rs"
|
||||
anyhow.workspace = true
|
||||
clap.workspace = true
|
||||
clap_complete.workspace = true
|
||||
deepseek-agent = { path = "../agent", version = "0.8.13" }
|
||||
deepseek-app-server = { path = "../app-server", version = "0.8.13" }
|
||||
deepseek-config = { path = "../config", version = "0.8.13" }
|
||||
deepseek-execpolicy = { path = "../execpolicy", version = "0.8.13" }
|
||||
deepseek-mcp = { path = "../mcp", version = "0.8.13" }
|
||||
deepseek-secrets = { path = "../secrets", version = "0.8.13" }
|
||||
deepseek-state = { path = "../state", version = "0.8.13" }
|
||||
deepseek-agent = { path = "../agent", version = "0.8.14" }
|
||||
deepseek-app-server = { path = "../app-server", version = "0.8.14" }
|
||||
deepseek-config = { path = "../config", version = "0.8.14" }
|
||||
deepseek-execpolicy = { path = "../execpolicy", version = "0.8.14" }
|
||||
deepseek-mcp = { path = "../mcp", version = "0.8.14" }
|
||||
deepseek-secrets = { path = "../secrets", version = "0.8.14" }
|
||||
deepseek-state = { path = "../state", version = "0.8.14" }
|
||||
chrono.workspace = true
|
||||
dirs.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
@@ -8,7 +8,7 @@ description = "Config schema and precedence model for DeepSeek workspace archite
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
deepseek-secrets = { path = "../secrets", version = "0.8.13" }
|
||||
deepseek-secrets = { path = "../secrets", version = "0.8.14" }
|
||||
dirs.workspace = true
|
||||
serde.workspace = true
|
||||
toml.workspace = true
|
||||
|
||||
@@ -9,13 +9,13 @@ description = "Core runtime boundaries for DeepSeek workspace architecture"
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
chrono.workspace = true
|
||||
deepseek-agent = { path = "../agent", version = "0.8.13" }
|
||||
deepseek-config = { path = "../config", version = "0.8.13" }
|
||||
deepseek-execpolicy = { path = "../execpolicy", version = "0.8.13" }
|
||||
deepseek-hooks = { path = "../hooks", version = "0.8.13" }
|
||||
deepseek-mcp = { path = "../mcp", version = "0.8.13" }
|
||||
deepseek-protocol = { path = "../protocol", version = "0.8.13" }
|
||||
deepseek-state = { path = "../state", version = "0.8.13" }
|
||||
deepseek-tools = { path = "../tools", version = "0.8.13" }
|
||||
deepseek-agent = { path = "../agent", version = "0.8.14" }
|
||||
deepseek-config = { path = "../config", version = "0.8.14" }
|
||||
deepseek-execpolicy = { path = "../execpolicy", version = "0.8.14" }
|
||||
deepseek-hooks = { path = "../hooks", version = "0.8.14" }
|
||||
deepseek-mcp = { path = "../mcp", version = "0.8.14" }
|
||||
deepseek-protocol = { path = "../protocol", version = "0.8.14" }
|
||||
deepseek-state = { path = "../state", version = "0.8.14" }
|
||||
deepseek-tools = { path = "../tools", version = "0.8.14" }
|
||||
serde_json.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
@@ -8,5 +8,5 @@ description = "Execution policy and approval model parity for DeepSeek workspace
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
deepseek-protocol = { path = "../protocol", version = "0.8.13" }
|
||||
deepseek-protocol = { path = "../protocol", version = "0.8.14" }
|
||||
serde.workspace = true
|
||||
|
||||
@@ -10,7 +10,7 @@ description = "Hook dispatch and notifications parity for DeepSeek workspace arc
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
chrono.workspace = true
|
||||
deepseek-protocol = { path = "../protocol", version = "0.8.13" }
|
||||
deepseek-protocol = { path = "../protocol", version = "0.8.14" }
|
||||
reqwest.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -9,7 +9,7 @@ description = "Tool invocation lifecycle, schema validation, and scheduler paral
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
deepseek-protocol = { path = "../protocol", version = "0.8.13" }
|
||||
deepseek-protocol = { path = "../protocol", version = "0.8.14" }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
tokio.workspace = true
|
||||
|
||||
@@ -21,8 +21,8 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
arboard = "3.4"
|
||||
deepseek-secrets = { path = "../secrets", version = "0.8.13" }
|
||||
deepseek-tools = { path = "../tools", version = "0.8.13" }
|
||||
deepseek-secrets = { path = "../secrets", version = "0.8.14" }
|
||||
deepseek-tools = { path = "../tools", version = "0.8.14" }
|
||||
schemaui = { version = "0.12.0", default-features = false, optional = true }
|
||||
async-stream = "0.3.6"
|
||||
async-trait = "0.1"
|
||||
|
||||
@@ -806,13 +806,22 @@ pub(super) fn parse_usage(usage: Option<&Value>) -> Usage {
|
||||
.and_then(|u| u.get("input_tokens").or_else(|| u.get("prompt_tokens")))
|
||||
.and_then(Value::as_u64)
|
||||
.unwrap_or(0);
|
||||
let output_tokens = usage
|
||||
let mut output_tokens = usage
|
||||
.and_then(|u| {
|
||||
u.get("output_tokens")
|
||||
.or_else(|| u.get("completion_tokens"))
|
||||
})
|
||||
.and_then(Value::as_u64)
|
||||
.unwrap_or(0);
|
||||
let reasoning_tokens_raw = usage
|
||||
.and_then(|u| u.get("completion_tokens_details"))
|
||||
.and_then(|details| details.get("reasoning_tokens"))
|
||||
.and_then(Value::as_u64);
|
||||
if output_tokens == 0
|
||||
&& let Some(reasoning_tokens) = reasoning_tokens_raw
|
||||
{
|
||||
output_tokens = reasoning_tokens;
|
||||
}
|
||||
let cached_tokens = usage
|
||||
.and_then(|u| u.get("prompt_tokens_details"))
|
||||
.and_then(|details| details.get("cached_tokens"))
|
||||
@@ -827,11 +836,7 @@ pub(super) fn parse_usage(usage: Option<&Value>) -> Usage {
|
||||
.and_then(Value::as_u64)
|
||||
.or_else(|| cached_tokens.map(|cached| input_tokens.saturating_sub(cached)))
|
||||
.map(|v| v as u32);
|
||||
let reasoning_tokens = usage
|
||||
.and_then(|u| u.get("completion_tokens_details"))
|
||||
.and_then(|details| details.get("reasoning_tokens"))
|
||||
.and_then(Value::as_u64)
|
||||
.map(|v| v as u32);
|
||||
let reasoning_tokens = reasoning_tokens_raw.map(|v| v as u32);
|
||||
|
||||
let server_tool_use = usage.and_then(|u| u.get("server_tool_use")).map(|server| {
|
||||
let code_execution_requests = server
|
||||
@@ -1694,6 +1699,26 @@ mod tests {
|
||||
assert_eq!(usage.reasoning_tokens, Some(12));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_usage_counts_reasoning_tokens_when_completion_tokens_are_zero() {
|
||||
let usage = parse_usage(Some(&json!({
|
||||
"prompt_tokens": 100,
|
||||
"completion_tokens": 0,
|
||||
"completion_tokens_details": {
|
||||
"reasoning_tokens": 12
|
||||
}
|
||||
})));
|
||||
|
||||
assert_eq!(usage.input_tokens, 100);
|
||||
assert_eq!(usage.output_tokens, 12);
|
||||
assert_eq!(usage.reasoning_tokens, Some(12));
|
||||
assert!(
|
||||
crate::pricing::calculate_turn_cost_from_usage("deepseek-v4-pro", &usage)
|
||||
.expect("DeepSeek V4 Pro pricing should apply")
|
||||
> 0.0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_usage_reads_v4_prompt_tokens_details_cached_tokens() {
|
||||
let usage = parse_usage(Some(&json!({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "deepseek-tui",
|
||||
"version": "0.8.13",
|
||||
"deepseekBinaryVersion": "0.8.13",
|
||||
"version": "0.8.14",
|
||||
"deepseekBinaryVersion": "0.8.14",
|
||||
"description": "Install and run deepseek and deepseek-tui binaries from GitHub release artifacts.",
|
||||
"author": "Hmbown",
|
||||
"license": "MIT",
|
||||
|
||||
Reference in New Issue
Block a user