Fix OpenAI reasoning round-trip: use store: false with encrypted_content

The OpenAI Responses API requires store: false for non-Azure endpoints.
Reasoning items round-trip correctly by requesting encrypted_content
via the `include` field, which embeds them in the response payload
rather than relying on server-side storage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-21 11:46:01 -04:00
parent 025316500d
commit f307c1fbdf
2 changed files with 24 additions and 12 deletions

View file

@ -882,7 +882,10 @@ fn validate_deep_result(
) -> (cli_table::Color, String) {
// Check tool use: need at least 2 steps (tool call + follow-up)
if result.steps.len() < 2 {
return (Color::Red, "deep: fail (model did not call tool)".to_string());
return (
Color::Red,
"deep: fail (model did not call tool)".to_string(),
);
}
// Check that step 0 had tool results (tool was executed)
@ -1042,11 +1045,8 @@ async fn test_one_model(info: &ModelInfo, deep: bool) -> (Color, String) {
match build_deep_test_params(info) {
None => (Color::Yellow, "deep: skipped (no tool support)".to_string()),
Some(params) => {
let result = tokio::time::timeout(
Duration::from_secs(90),
generate::generate(params),
)
.await;
let result =
tokio::time::timeout(Duration::from_secs(90), generate::generate(params)).await;
match result {
Ok(Ok(ref gen_result)) => validate_deep_result(gen_result, info),
Ok(Err(e)) => (Color::Red, format!("deep: error: {e}")),
@ -1094,16 +1094,15 @@ async fn test_models(
let test_kind = if deep { "Deep testing" } else { "Testing" };
let pb = indicatif::ProgressBar::new(models_to_test.len() as u64);
pb.set_style(
indicatif::ProgressStyle::with_template(
&format!("{{spinner:.green}} {test_kind} {{pos}}/{{len}} models {{wide_bar}} {{eta}}"),
)
indicatif::ProgressStyle::with_template(&format!(
"{{spinner:.green}} {test_kind} {{pos}}/{{len}} models {{wide_bar}} {{eta}}"
))
.unwrap(),
);
pb.enable_steady_tick(Duration::from_millis(100));
// Build (original_index, model_info) pairs, then shuffle for provider spread
let mut indexed: Vec<(usize, &ModelInfo)> =
models_to_test.iter().enumerate().collect();
let mut indexed: Vec<(usize, &ModelInfo)> = models_to_test.iter().enumerate().collect();
indexed.shuffle(&mut rand::thread_rng());
// Run tests concurrently, 6 at a time

View file

@ -138,6 +138,8 @@ struct ApiRequest {
#[serde(skip_serializing_if = "Option::is_none")]
metadata: Option<std::collections::HashMap<String, String>>,
store: bool,
#[serde(skip_serializing_if = "Vec::is_empty")]
include: Vec<String>,
#[serde(skip_serializing_if = "std::ops::Not::not")]
stream: bool,
}
@ -391,6 +393,12 @@ fn build_api_request(request: &Request, stream: bool, codex_mode: bool) -> ApiRe
.as_ref()
.and_then(translate_response_format);
let include = if reasoning.is_some() {
vec!["reasoning.encrypted_content".to_string()]
} else {
Vec::new()
};
let instructions = if codex_mode {
Some(instructions.unwrap_or_default())
} else {
@ -414,7 +422,12 @@ fn build_api_request(request: &Request, stream: bool, codex_mode: bool) -> ApiRe
text,
stop: request.stop_sequences.clone(),
metadata: request.metadata.clone(),
store: true,
// store: false is required for non-Azure OpenAI endpoints. Reasoning
// items still round-trip correctly because we request encrypted_content
// via the `include` field, which embeds them in the response payload
// rather than relying on server-side storage.
store: false,
include,
stream,
}
}