From 9a40d2c4172bb155a954870d4710de241d593aca Mon Sep 17 00:00:00 2001 From: Amplifier Resolve Date: Tue, 16 Jun 2026 23:58:16 +0000 Subject: [PATCH] fix(core): add cost_usd field to Rust Usage struct and wire through conversions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add cost_usd: Option field to Rust messages::Usage struct - Wire cost_usd through both native→proto and proto→native conversions - Add cost_usd roundtrip test to verify field preservation - Add cost_usd to proto Usage message (field 7) - Fix test docstring: estimated_cost was deprecated, not removed - Regenerate uv.lock (1.5.1 → 1.6.0) Fixes the gap where Python Usage had cost_usd but Rust did not, causing conversion layer to hardcode None or drop incoming values. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> --- bindings/python/tests/test_cost_models.py | 2 +- .../src/generated/conversions.rs | 36 +++++++++++++++++++ crates/amplifier-core/src/messages.rs | 2 ++ proto/amplifier_module.proto | 1 + uv.lock | 2 +- 5 files changed, 41 insertions(+), 2 deletions(-) diff --git a/bindings/python/tests/test_cost_models.py b/bindings/python/tests/test_cost_models.py index facfdb0f..096eeada 100644 --- a/bindings/python/tests/test_cost_models.py +++ b/bindings/python/tests/test_cost_models.py @@ -5,7 +5,7 @@ - Pydantic validates Decimal type — rejects float - None means unknown (not zero) - Decimal("0") means explicitly free -- SessionStatus.estimated_cost is removed (was never populated) +- SessionStatus.estimated_cost is deprecated (was never populated) """ from decimal import Decimal diff --git a/crates/amplifier-core/src/generated/conversions.rs b/crates/amplifier-core/src/generated/conversions.rs index 39b06cfa..cc95ef67 100644 --- a/crates/amplifier-core/src/generated/conversions.rs +++ b/crates/amplifier-core/src/generated/conversions.rs @@ -168,6 +168,7 @@ impl From for super::amplifier_module::Usage { i32::MAX }) }), + cost_usd: native.cost_usd.clone(), } } } @@ -181,6 +182,7 @@ impl From for crate::messages::Usage { reasoning_tokens: proto.reasoning_tokens.map(i64::from), cache_read_tokens: proto.cache_read_tokens.map(i64::from), cache_write_tokens: proto.cache_creation_tokens.map(i64::from), + cost_usd: proto.cost_usd.clone(), extensions: HashMap::new(), } } @@ -1111,6 +1113,40 @@ mod tests { ); } + /// Verify that cost_usd survives roundtrip (None and Some cases). + #[test] + fn usage_cost_usd_roundtrips_correctly() { + // Test Some case + let original_with_cost = crate::messages::Usage { + input_tokens: 100, + output_tokens: 50, + total_tokens: 150, + reasoning_tokens: None, + cache_read_tokens: None, + cache_write_tokens: None, + cost_usd: Some("0.123".to_string()), + extensions: HashMap::new(), + }; + let proto: super::super::amplifier_module::Usage = original_with_cost.clone().into(); + let restored: crate::messages::Usage = proto.into(); + assert_eq!(original_with_cost.cost_usd, restored.cost_usd); + + // Test None case + let original_without_cost = crate::messages::Usage { + input_tokens: 100, + output_tokens: 50, + total_tokens: 150, + reasoning_tokens: None, + cache_read_tokens: None, + cache_write_tokens: None, + cost_usd: None, + extensions: HashMap::new(), + }; + let proto: super::super::amplifier_module::Usage = original_without_cost.clone().into(); + let restored: crate::messages::Usage = proto.into(); + assert_eq!(original_without_cost.cost_usd, restored.cost_usd); + } + // -- E-3: ModelInfo i64→i32 overflow clamps to i32::MAX -- #[test] diff --git a/crates/amplifier-core/src/messages.rs b/crates/amplifier-core/src/messages.rs index bb0e746d..f1b8b9a5 100644 --- a/crates/amplifier-core/src/messages.rs +++ b/crates/amplifier-core/src/messages.rs @@ -275,6 +275,8 @@ pub struct Usage { pub cache_read_tokens: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub cache_write_tokens: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub cost_usd: Option, #[serde(flatten)] pub extensions: HashMap, } diff --git a/proto/amplifier_module.proto b/proto/amplifier_module.proto index e12f988a..47ff700e 100644 --- a/proto/amplifier_module.proto +++ b/proto/amplifier_module.proto @@ -305,6 +305,7 @@ message Usage { optional int32 reasoning_tokens = 4; optional int32 cache_read_tokens = 5; optional int32 cache_creation_tokens = 6; + optional string cost_usd = 7; } message Degradation { diff --git a/uv.lock b/uv.lock index 652a8412..c2886455 100644 --- a/uv.lock +++ b/uv.lock @@ -4,7 +4,7 @@ requires-python = ">=3.11" [[package]] name = "amplifier-core" -version = "1.5.1" +version = "1.6.0" source = { editable = "." } dependencies = [ { name = "click" },