From 7d384a6277c74fb35b8e51297ffbccda59746ce9 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 24 Aug 2026 04:15:49 +0200 Subject: [PATCH] Bound connector public output separately --- .../src/model_connector.rs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/epiphany-openai-runtime/src/model_connector.rs b/epiphany-openai-runtime/src/model_connector.rs index 5774bfcd..f9a243f3 100644 --- a/epiphany-openai-runtime/src/model_connector.rs +++ b/epiphany-openai-runtime/src/model_connector.rs @@ -505,7 +505,7 @@ fn sanitize_model_events( } EpiphanyModelStreamPayload::Completed { receipt } => { if let Some(limit) = request.max_output_tokens - && !matches!(receipt.output_tokens, Some(tokens) if tokens <= u64::from(limit)) + && !receipt_proves_public_output_bound(&receipt, limit) { return vec![connector_failed_event( &request.request_id, @@ -538,6 +538,23 @@ fn sanitize_model_events( sanitized } +fn receipt_proves_public_output_bound( + receipt: &epiphany_model_adapter::EpiphanyModelReceipt, + limit: u32, +) -> bool { + let Some(total_output_tokens) = receipt.output_tokens else { + return false; + }; + let public_output_tokens = match receipt.reasoning_output_tokens { + Some(reasoning_tokens) if reasoning_tokens <= total_output_tokens => { + total_output_tokens - reasoning_tokens + } + Some(_) => return false, + None => total_output_tokens, + }; + public_output_tokens <= u64::from(limit) +} + fn connector_failed_event(request_id: &str, message: &str) -> EpiphanyModelStreamEvent { EpiphanyModelStreamEvent { schema_id: MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), @@ -947,6 +964,20 @@ mod tests { } } + #[test] + fn connector_bounds_public_output_without_exposing_hidden_reasoning() { + let mut receipt = EpiphanyModelReceipt::new("request-1", "openai-codex", "gpt-5.4"); + receipt.output_tokens = Some(700); + receipt.reasoning_output_tokens = Some(300); + assert!(receipt_proves_public_output_bound(&receipt, 512)); + + receipt.reasoning_output_tokens = None; + assert!(!receipt_proves_public_output_bound(&receipt, 512)); + + receipt.reasoning_output_tokens = Some(701); + assert!(!receipt_proves_public_output_bound(&receipt, 512)); + } + #[test] fn encrypted_result_refuses_outer_identity_substitution() -> Result<()> { let security = CultNetClientSecurityOptions::new("not-a-production-secret")?;