From 86423ad1d0d1dd77959b8f40791c25e5680cec0d Mon Sep 17 00:00:00 2001 From: Jean Mertz Date: Thu, 19 Mar 2026 12:07:14 +0100 Subject: [PATCH 1/2] feat(llm, openai): Add GPT-5.4 family and `none` reasoning effort MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a `none` field to `ReasoningDetails::Leveled` to indicate that a model supports completely disabling reasoning. This is now the first candidate returned by `lowest_effort()`, sitting below `xlow`. Several new OpenAI models are added: `gpt-5.4`, `gpt-5.4-pro`, `gpt-5.4-mini`, `gpt-5.4-nano`, `gpt-5.3-codex`, and `gpt-5-pro`. All of these carry the new `TEMP_REQUIRES_NO_REASONING` feature flag, which is also applied retroactively to the existing GPT-5 and GPT-5.1/ 5.2 families. When this flag is set and reasoning effort is anything other than `none`, `temperature` and `top_p` are stripped from the request and a warning is emitted — matching the constraint the OpenAI API enforces for these models. Two further improvements land for the OpenAI provider. Response verbosity (`low`/`medium`/`high`) can now be set via the catch-all `parameters.other.verbosity` config key, and is forwarded to the API through `TextConfig`. Message phase (`commentary`/`final_answer`) is now round-tripped; it is persisted to conversation metadata under the `openai_phase` key when a response is received, and restored when that message is later sent back as input context. The Google provider is updated to handle the new `none` field in pattern matches, and some model reasoning capability flags are corrected to better reflect reality. Signed-off-by: Jean Mertz --- Cargo.lock | 61 +---- crates/jp_llm/src/model.rs | 19 +- crates/jp_llm/src/provider/google.rs | 13 +- crates/jp_llm/src/provider/openai.rs | 216 ++++++++++++++++-- .../fixtures/google/test_models__models.snap | 6 + .../test_model_details__model_details.snap | 4 +- .../fixtures/openai/test_models__models.snap | 97 ++++++-- 7 files changed, 311 insertions(+), 105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff26e7f2..c0b47051 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1540,7 +1540,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.56.0", + "windows-core", ] [[package]] @@ -2577,7 +2577,7 @@ dependencies = [ [[package]] name = "openai_responses" version = "0.1.6" -source = "git+https://github.com/JeanMertz/openai-responses-rs#87a31cd3ebac62f83efef2586c34a50141d33ffe" +source = "git+https://github.com/JeanMertz/openai-responses-rs#1f5761a9c7361f5bc56725d15c1309c72f52af5e" dependencies = [ "async-fn-stream", "chrono", @@ -4639,7 +4639,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", - "windows-core 0.61.2", + "windows-core", "windows-future", "windows-link 0.1.3", "windows-numerics", @@ -4651,19 +4651,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.56.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" -dependencies = [ - "windows-implement 0.56.0", - "windows-interface 0.56.0", - "windows-result 0.1.2", - "windows-targets 0.52.6", + "windows-core", ] [[package]] @@ -4672,10 +4660,10 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", + "windows-implement", + "windows-interface", "windows-link 0.1.3", - "windows-result 0.3.4", + "windows-result", "windows-strings", ] @@ -4685,22 +4673,11 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core 0.61.2", + "windows-core", "windows-link 0.1.3", "windows-threading", ] -[[package]] -name = "windows-implement" -version = "0.56.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-implement" version = "0.60.2" @@ -4712,17 +4689,6 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-interface" -version = "0.56.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-interface" version = "0.59.3" @@ -4752,19 +4718,10 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core 0.61.2", + "windows-core", "windows-link 0.1.3", ] -[[package]] -name = "windows-result" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-result" version = "0.3.4" diff --git a/crates/jp_llm/src/model.rs b/crates/jp_llm/src/model.rs index 9034745c..49e0fce4 100644 --- a/crates/jp_llm/src/model.rs +++ b/crates/jp_llm/src/model.rs @@ -123,6 +123,7 @@ impl ModelDetails { // Leveled Some(ReasoningDetails::Leveled { + none: _, xlow: _, low, medium, @@ -225,6 +226,9 @@ pub enum ReasoningDetails { /// reasoning configuration, but instead offer specific "efforts" of /// reasoning, such as low/medium/high effort. Leveled { + /// Whether the model supports completely disabling reasoning. + none: bool, + /// Whether the model supports extremely low effort reasoning. xlow: bool, @@ -265,8 +269,16 @@ impl ReasoningDetails { #[must_use] #[expect(clippy::fn_params_excessive_bools)] - pub fn leveled(xlow: bool, low: bool, medium: bool, high: bool, xhigh: bool) -> Self { + pub fn leveled( + none: bool, + xlow: bool, + low: bool, + medium: bool, + high: bool, + xhigh: bool, + ) -> Self { Self::Leveled { + none, xlow, low, medium, @@ -312,13 +324,16 @@ impl ReasoningDetails { pub fn lowest_effort(&self) -> Option { match self { Self::Leveled { + none, xlow, low, medium, high, xhigh, } => { - if *xlow { + if *none { + Some(ReasoningEffort::None) + } else if *xlow { Some(ReasoningEffort::Xlow) } else if *low { Some(ReasoningEffort::Low) diff --git a/crates/jp_llm/src/provider/google.rs b/crates/jp_llm/src/provider/google.rs index 075e2037..09d78309 100644 --- a/crates/jp_llm/src/provider/google.rs +++ b/crates/jp_llm/src/provider/google.rs @@ -203,6 +203,7 @@ fn create_request( }, thinking_level: match details { ReasoningDetails::Leveled { + none: _, xlow, low, medium, @@ -370,7 +371,9 @@ fn map_model(model: types::Model) -> ModelDetails { display_name, context_window, max_output_tokens, - reasoning: Some(ReasoningDetails::leveled(false, true, true, true, false)), + reasoning: Some(ReasoningDetails::leveled( + false, false, true, true, true, false, + )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 1, 1).unwrap()), deprecated: Some(ModelDeprecation::Active), structured_output: None, @@ -382,7 +385,9 @@ fn map_model(model: types::Model) -> ModelDetails { display_name, context_window, max_output_tokens, - reasoning: Some(ReasoningDetails::leveled(false, true, false, true, false)), + reasoning: Some(ReasoningDetails::leveled( + false, false, true, false, true, false, + )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 1, 1).unwrap()), deprecated: Some(ModelDeprecation::Active), structured_output: None, @@ -393,7 +398,9 @@ fn map_model(model: types::Model) -> ModelDetails { display_name, context_window, max_output_tokens, - reasoning: Some(ReasoningDetails::leveled(true, true, true, true, false)), + reasoning: Some(ReasoningDetails::leveled( + false, true, true, true, true, false, + )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 1, 1).unwrap()), deprecated: Some(ModelDeprecation::Active), structured_output: None, diff --git a/crates/jp_llm/src/provider/openai.rs b/crates/jp_llm/src/provider/openai.rs index df74eaed..af1aeea0 100644 --- a/crates/jp_llm/src/provider/openai.rs +++ b/crates/jp_llm/src/provider/openai.rs @@ -42,6 +42,11 @@ static PROVIDER: ProviderId = ProviderId::Openai; pub(crate) const ITEM_ID_KEY: &str = "openai_item_id"; pub(crate) const ENCRYPTED_CONTENT_KEY: &str = "openai_encrypted_content"; +pub(crate) const PHASE_KEY: &str = "openai_phase"; + +/// Feature flag: temperature and `top_p` are only supported when reasoning +/// effort is `none`. GPT-5 family models have this constraint. +const TEMP_REQUIRES_NO_REASONING: &str = "temp_requires_no_reasoning"; #[derive(Debug, Clone)] pub struct Openai { @@ -117,6 +122,7 @@ pub(crate) struct ModelResponse { /// Create a request for the given model and query details. /// /// Returns `(request, is_structured, reasoning_enabled)`. +#[expect(clippy::too_many_lines)] fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Request, bool, bool)> { let ChatQuery { thread, @@ -124,20 +130,42 @@ fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Request, bo tool_choice, } = query; - // Only use the schema if the very last event is a ChatRequest with one. - // Transform the schema for OpenAI's strict structured output mode - // before passing it to the request. - let text = thread.events.schema().map(|schema| types::TextConfig { - format: types::TextFormat::JsonSchema { - schema: Value::Object(transform_schema(schema)), - description: "Structured output".to_owned(), - name: "structured_output".to_owned(), - strict: Some(true), - }, - }); + let parameters = thread.events.config()?.assistant.model.parameters; + + // Parse verbosity from the catch-all parameters map. + let verbosity = parameters + .other + .get("verbosity") + .and_then(|v| v.as_str()) + .and_then(|s| match s { + "low" => Some(types::TextVerbosity::Low), + "medium" => Some(types::TextVerbosity::Medium), + "high" => Some(types::TextVerbosity::High), + _ => { + warn!(verbosity = s, "Unknown verbosity value, ignoring."); + None + } + }); + + // Build the text config from structured output schema and/or verbosity. + // Transform the schema for OpenAI's strict structured output mode. + let text = match thread.events.schema() { + Some(schema) => Some(types::TextConfig { + format: types::TextFormat::JsonSchema { + schema: Value::Object(transform_schema(schema)), + description: "Structured output".to_owned(), + name: "structured_output".to_owned(), + strict: Some(true), + }, + verbosity, + }), + None => verbosity.map(|v| types::TextConfig { + format: types::TextFormat::default(), + verbosity: Some(v), + }), + }; let is_structured = text.is_some(); - let parameters = thread.events.config()?.assistant.model.parameters; let supports_reasoning = model .reasoning .is_some_and(|v| !matches!(v, ReasoningDetails::Unsupported)); @@ -216,9 +244,39 @@ fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Request, bo messages.push(types::InputListItem::Message(types::InputMessage { role: types::Role::User, content: types::ContentInput::List(attachment_items), + phase: None, })); } + // GPT-5 family models reject temperature/top_p when reasoning is active + // (any effort other than `none`). Strip them and warn if configured. + let strip_temp = model.features.contains(&TEMP_REQUIRES_NO_REASONING) + && reasoning + .as_ref() + .is_some_and(|r| !matches!(r.effort, Some(types::ReasoningEffort::None))); + let temperature = if strip_temp { + if parameters.temperature.is_some() { + warn!( + model = %model.id, + "temperature is not supported when reasoning is active; ignoring" + ); + } + None + } else { + parameters.temperature + }; + let top_p = if strip_temp { + if parameters.top_p.is_some() { + warn!( + model = %model.id, + "top_p is not supported when reasoning is active; ignoring" + ); + } + None + } else { + parameters.top_p + }; + messages.extend(convert_events(supports_reasoning)(parts.events)); let request = Request { model: types::Model::Other(model.id.name.to_string()), @@ -227,11 +285,11 @@ fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Request, bo store: Some(false), tool_choice: Some(convert_tool_choice(tool_choice)), tools: Some(convert_tools(tools)), - temperature: parameters.temperature, + temperature, reasoning, max_output_tokens: parameters.max_tokens.map(Into::into), truncation: Some(types::Truncation::Auto), - top_p: parameters.top_p, + top_p, text, ..Default::default() }; @@ -244,16 +302,83 @@ fn create_request(model: &ModelDetails, query: ChatQuery) -> Result<(Request, bo #[expect(clippy::too_many_lines)] fn map_model(model: ModelResponse) -> Result { let details = match model.id.as_str() { + "gpt-5.4" | "gpt-5.4-2026-03-05" => ModelDetails { + id: (PROVIDER, model.id).try_into()?, + display_name: Some("GPT-5.4".to_owned()), + context_window: Some(1_050_000), + max_output_tokens: Some(128_000), + reasoning: Some(ReasoningDetails::leveled( + true, false, true, true, true, true, + )), + knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 8, 31).unwrap()), + deprecated: Some(ModelDeprecation::Active), + structured_output: None, + features: vec![TEMP_REQUIRES_NO_REASONING], + }, + "gpt-5.4-pro" | "gpt-5.4-pro-2026-03-05" => ModelDetails { + id: (PROVIDER, model.id).try_into()?, + display_name: Some("GPT-5.4 pro".to_owned()), + context_window: Some(1_050_000), + max_output_tokens: Some(128_000), + reasoning: Some(ReasoningDetails::leveled( + false, false, false, true, true, true, + )), + knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 8, 31).unwrap()), + deprecated: Some(ModelDeprecation::Active), + structured_output: None, + features: vec![TEMP_REQUIRES_NO_REASONING], + }, + "gpt-5.4-mini" | "gpt-5.4-mini-2026-03-17" => ModelDetails { + id: (PROVIDER, model.id).try_into()?, + display_name: Some("GPT-5.4 mini".to_owned()), + context_window: Some(400_000), + max_output_tokens: Some(128_000), + reasoning: Some(ReasoningDetails::leveled( + true, false, true, true, true, true, + )), + knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 8, 31).unwrap()), + deprecated: Some(ModelDeprecation::Active), + structured_output: None, + features: vec![TEMP_REQUIRES_NO_REASONING], + }, + "gpt-5.4-nano" | "gpt-5.4-nano-2026-03-17" => ModelDetails { + id: (PROVIDER, model.id).try_into()?, + display_name: Some("GPT-5.4 nano".to_owned()), + context_window: Some(400_000), + max_output_tokens: Some(128_000), + reasoning: Some(ReasoningDetails::leveled( + true, false, true, true, true, true, + )), + knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 8, 31).unwrap()), + deprecated: Some(ModelDeprecation::Active), + structured_output: None, + features: vec![TEMP_REQUIRES_NO_REASONING], + }, + "gpt-5.3-codex" => ModelDetails { + id: (PROVIDER, model.id).try_into()?, + display_name: Some("GPT-5.3 Codex".to_owned()), + context_window: Some(400_000), + max_output_tokens: Some(128_000), + reasoning: Some(ReasoningDetails::leveled( + false, false, true, true, true, true, + )), + knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 8, 31).unwrap()), + deprecated: Some(ModelDeprecation::Active), + structured_output: None, + features: vec![TEMP_REQUIRES_NO_REASONING], + }, "gpt-5.2-pro" | "gpt-5.2-pro-2025-12-11" => ModelDetails { id: (PROVIDER, model.id).try_into()?, display_name: Some("GPT-5.2 pro".to_owned()), context_window: Some(400_000), max_output_tokens: Some(128_000), - reasoning: Some(ReasoningDetails::leveled(false, false, true, true, true)), + reasoning: Some(ReasoningDetails::leveled( + false, false, false, true, true, true, + )), knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 9, 1).unwrap()), deprecated: Some(ModelDeprecation::Active), structured_output: None, - features: vec![], + features: vec![TEMP_REQUIRES_NO_REASONING], }, "gpt-5.2" | "gpt-5.2-2025-12-11" => ModelDetails { id: (PROVIDER, model.id).try_into()?, @@ -264,7 +389,7 @@ fn map_model(model: ModelResponse) -> Result { knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2025, 9, 1).unwrap()), deprecated: Some(ModelDeprecation::Active), structured_output: None, - features: vec![], + features: vec![TEMP_REQUIRES_NO_REASONING], }, "gpt-5.1-codex-max" => ModelDetails { id: (PROVIDER, model.id).try_into()?, @@ -275,7 +400,7 @@ fn map_model(model: ModelResponse) -> Result { knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 10, 1).unwrap()), deprecated: Some(ModelDeprecation::Active), structured_output: None, - features: vec![], + features: vec![TEMP_REQUIRES_NO_REASONING], }, "gpt-5.1-codex" => ModelDetails { id: (PROVIDER, model.id).try_into()?, @@ -286,7 +411,7 @@ fn map_model(model: ModelResponse) -> Result { knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 10, 1).unwrap()), deprecated: Some(ModelDeprecation::Active), structured_output: None, - features: vec![], + features: vec![TEMP_REQUIRES_NO_REASONING], }, "gpt-5.1-codex-mini" => ModelDetails { id: (PROVIDER, model.id).try_into()?, @@ -297,7 +422,7 @@ fn map_model(model: ModelResponse) -> Result { knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 10, 1).unwrap()), deprecated: Some(ModelDeprecation::Active), structured_output: None, - features: vec![], + features: vec![TEMP_REQUIRES_NO_REASONING], }, "gpt-5.1" | "gpt-5.1-2025-11-13" => ModelDetails { id: (PROVIDER, model.id).try_into()?, @@ -308,7 +433,7 @@ fn map_model(model: ModelResponse) -> Result { knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 10, 1).unwrap()), deprecated: Some(ModelDeprecation::Active), structured_output: None, - features: vec![], + features: vec![TEMP_REQUIRES_NO_REASONING], }, "gpt-5-codex" => ModelDetails { id: (PROVIDER, model.id).try_into()?, @@ -319,7 +444,7 @@ fn map_model(model: ModelResponse) -> Result { knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 10, 1).unwrap()), deprecated: Some(ModelDeprecation::Active), structured_output: None, - features: vec![], + features: vec![TEMP_REQUIRES_NO_REASONING], }, "gpt-5" | "gpt-5-2025-08-07" => ModelDetails { id: (PROVIDER, model.id).try_into()?, @@ -330,7 +455,20 @@ fn map_model(model: ModelResponse) -> Result { knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 8, 30).unwrap()), deprecated: Some(ModelDeprecation::Active), structured_output: None, - features: vec![], + features: vec![TEMP_REQUIRES_NO_REASONING], + }, + "gpt-5-pro" => ModelDetails { + id: (PROVIDER, model.id).try_into()?, + display_name: Some("GPT-5 pro".to_owned()), + context_window: Some(400_000), + max_output_tokens: Some(128_000), + reasoning: Some(ReasoningDetails::leveled( + false, false, false, false, true, false, + )), + knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 8, 30).unwrap()), + deprecated: Some(ModelDeprecation::Active), + structured_output: None, + features: vec![TEMP_REQUIRES_NO_REASONING], }, "gpt-5-chat-latest" => ModelDetails { id: (PROVIDER, model.id).try_into()?, @@ -341,7 +479,7 @@ fn map_model(model: ModelResponse) -> Result { knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 8, 30).unwrap()), deprecated: Some(ModelDeprecation::Active), structured_output: None, - features: vec![], + features: vec![TEMP_REQUIRES_NO_REASONING], }, "gpt-5-mini" | "gpt-5-mini-2025-08-07" => ModelDetails { id: (PROVIDER, model.id).try_into()?, @@ -352,7 +490,7 @@ fn map_model(model: ModelResponse) -> Result { knowledge_cutoff: Some(NaiveDate::from_ymd_opt(2024, 8, 30).unwrap()), deprecated: Some(ModelDeprecation::Active), structured_output: None, - features: vec![], + features: vec![TEMP_REQUIRES_NO_REASONING], }, "gpt-5-nano" | "gpt-5-nano-2025-08-07" => ModelDetails { id: (PROVIDER, model.id).try_into()?, @@ -661,6 +799,13 @@ fn map_event( types::OutputItem::Message(v) => { let mut map = IndexMap::new(); map.insert(ITEM_ID_KEY.to_owned(), v.id.clone().into()); + if let Some(phase) = &v.phase { + let phase_str = match phase { + types::Phase::Commentary => "commentary", + types::Phase::FinalAnswer => "final_answer", + }; + map.insert(PHASE_KEY.to_owned(), phase_str.into()); + } map } types::OutputItem::Reasoning(v) => { @@ -1131,9 +1276,23 @@ fn to_system_messages(parts: Vec) -> ListItem { .map(|text| types::ContentItem::Text { text }) .collect(), ), + phase: None, })) } +/// Parse a phase string from metadata into the API type. +fn parse_phase(metadata: &mut Map) -> Option { + metadata + .remove(PHASE_KEY) + .and_then(|v| v.as_str().map(str::to_owned)) + .and_then(|s| match s.as_str() { + "commentary" => Some(types::Phase::Commentary), + "final_answer" => Some(types::Phase::FinalAnswer), + _ => None, + }) +} + +#[expect(clippy::too_many_lines)] fn convert_events( supports_reasoning: bool, ) -> impl Fn(ConversationStream) -> Vec { @@ -1150,6 +1309,7 @@ fn convert_events( vec![types::InputListItem::Message(types::InputMessage { role: types::Role::User, content: types::ContentInput::Text(request.content), + phase: None, })] } EventKind::ChatResponse(response) => { @@ -1161,6 +1321,8 @@ fn convert_events( .remove(ENCRYPTED_CONTENT_KEY) .and_then(|v| v.as_str().map(str::to_owned)); + let phase = parse_phase(&mut metadata); + match response { ChatResponse::Reasoning { reasoning } => { if supports_reasoning && let Some(id) = id { @@ -1181,6 +1343,7 @@ fn convert_events( content: types::ContentInput::Text(format!( "\n{reasoning}\n\n\n", )), + phase, })] } } @@ -1195,12 +1358,14 @@ fn convert_events( annotations: vec![], }], status: types::MessageStatus::Completed, + phase, }), )] } else { vec![types::InputListItem::Message(types::InputMessage { role: types::Role::Assistant, content: types::ContentInput::Text(message), + phase, })] } } @@ -1208,6 +1373,7 @@ fn convert_events( vec![types::InputListItem::Message(types::InputMessage { role: types::Role::Assistant, content: types::ContentInput::Text(data.to_string()), + phase, })] } } diff --git a/crates/jp_llm/tests/fixtures/google/test_models__models.snap b/crates/jp_llm/tests/fixtures/google/test_models__models.snap index f0970e35..627e3bc0 100644 --- a/crates/jp_llm/tests/fixtures/google/test_models__models.snap +++ b/crates/jp_llm/tests/fixtures/google/test_models__models.snap @@ -415,6 +415,7 @@ expression: v ), reasoning: Some( Leveled { + none: false, xlow: true, low: true, medium: true, @@ -487,6 +488,7 @@ expression: v ), reasoning: Some( Leveled { + none: false, xlow: false, low: true, medium: true, @@ -619,6 +621,7 @@ expression: v ), reasoning: Some( Leveled { + none: false, xlow: false, low: true, medium: false, @@ -653,6 +656,7 @@ expression: v ), reasoning: Some( Leveled { + none: false, xlow: true, low: true, medium: true, @@ -687,6 +691,7 @@ expression: v ), reasoning: Some( Leveled { + none: false, xlow: false, low: true, medium: true, @@ -721,6 +726,7 @@ expression: v ), reasoning: Some( Leveled { + none: false, xlow: false, low: true, medium: true, diff --git a/crates/jp_llm/tests/fixtures/openai/test_model_details__model_details.snap b/crates/jp_llm/tests/fixtures/openai/test_model_details__model_details.snap index d4bf560f..6ec7ea49 100644 --- a/crates/jp_llm/tests/fixtures/openai/test_model_details__model_details.snap +++ b/crates/jp_llm/tests/fixtures/openai/test_model_details__model_details.snap @@ -32,6 +32,8 @@ expression: v Active, ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ] diff --git a/crates/jp_llm/tests/fixtures/openai/test_models__models.snap b/crates/jp_llm/tests/fixtures/openai/test_models__models.snap index b8bb7f49..d2e1d9ea 100644 --- a/crates/jp_llm/tests/fixtures/openai/test_models__models.snap +++ b/crates/jp_llm/tests/fixtures/openai/test_models__models.snap @@ -1624,7 +1624,9 @@ expression: v Active, ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { @@ -1655,7 +1657,9 @@ expression: v Active, ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { @@ -1686,7 +1690,9 @@ expression: v Active, ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { @@ -1717,7 +1723,9 @@ expression: v Active, ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { @@ -1748,7 +1756,9 @@ expression: v Active, ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { @@ -1905,7 +1915,9 @@ expression: v Active, ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { @@ -1946,14 +1958,35 @@ expression: v "gpt-5-pro", ), }, - display_name: None, - context_window: None, - max_output_tokens: None, - reasoning: None, - knowledge_cutoff: None, - deprecated: None, + display_name: Some( + "GPT-5 pro", + ), + context_window: Some( + 400000, + ), + max_output_tokens: Some( + 128000, + ), + reasoning: Some( + Leveled { + none: false, + xlow: false, + low: false, + medium: false, + high: true, + xhigh: false, + }, + ), + knowledge_cutoff: Some( + 2024-08-30, + ), + deprecated: Some( + Active, + ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { @@ -2128,7 +2161,9 @@ expression: v Active, ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { @@ -2159,7 +2194,9 @@ expression: v Active, ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { @@ -2190,7 +2227,9 @@ expression: v Active, ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { @@ -2221,7 +2260,9 @@ expression: v Active, ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { @@ -2252,7 +2293,9 @@ expression: v Active, ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { @@ -2299,7 +2342,9 @@ expression: v Active, ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { @@ -2330,7 +2375,9 @@ expression: v Active, ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { @@ -2350,6 +2397,7 @@ expression: v ), reasoning: Some( Leveled { + none: false, xlow: false, low: false, medium: true, @@ -2364,7 +2412,9 @@ expression: v Active, ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { @@ -2384,6 +2434,7 @@ expression: v ), reasoning: Some( Leveled { + none: false, xlow: false, low: false, medium: true, @@ -2398,7 +2449,9 @@ expression: v Active, ), structured_output: None, - features: [], + features: [ + "temp_requires_no_reasoning", + ], }, ModelDetails { id: ModelIdConfig { From d1781fb63f53fc1284f7245b071f7d690029345a Mon Sep 17 00:00:00 2001 From: Jean Mertz Date: Thu, 19 Mar 2026 12:12:12 +0100 Subject: [PATCH 2/2] fixup! feat(llm, openai): Add GPT-5.4 family and `none` reasoning effort Signed-off-by: Jean Mertz --- .config/supply-chain/audits.toml | 11 ++++++++++ .config/supply-chain/config.toml | 8 -------- .config/supply-chain/imports.lock | 34 ++++++------------------------- 3 files changed, 17 insertions(+), 36 deletions(-) diff --git a/.config/supply-chain/audits.toml b/.config/supply-chain/audits.toml index 0b002849..6c9e8800 100644 --- a/.config/supply-chain/audits.toml +++ b/.config/supply-chain/audits.toml @@ -41,6 +41,17 @@ who = "Jean Mertz " criteria = "safe-to-deploy" delta = "0.3.3 -> 0.3.4" +[[audits.openai_responses]] +who = "Jean Mertz " +criteria = "safe-to-deploy" +version = "0.1.6" + +[[audits.openai_responses]] +who = "Jean Mertz " +criteria = "safe-to-deploy" +delta = "0.1.6 -> 0.1.6@git:1f5761a9c7361f5bc56725d15c1309c72f52af5e" +importable = false + [[audits.pastey]] who = "Jean Mertz " criteria = "safe-to-deploy" diff --git a/.config/supply-chain/config.toml b/.config/supply-chain/config.toml index d4b6608d..00efa0dc 100644 --- a/.config/supply-chain/config.toml +++ b/.config/supply-chain/config.toml @@ -445,18 +445,10 @@ criteria = "safe-to-deploy" version = "1.1.0" criteria = "safe-to-deploy" -[[exemptions.openai_responses]] -version = "0.1.6@git:87a31cd3ebac62f83efef2586c34a50141d33ffe" -criteria = "safe-to-deploy" - [[exemptions.os_pipe]] version = "1.2.2" criteria = "safe-to-deploy" -[[exemptions.parking]] -version = "2.2.1" -criteria = "safe-to-deploy" - [[exemptions.path-tree]] version = "0.8.3" criteria = "safe-to-deploy" diff --git a/.config/supply-chain/imports.lock b/.config/supply-chain/imports.lock index d4bb975d..86f99e7e 100644 --- a/.config/supply-chain/imports.lock +++ b/.config/supply-chain/imports.lock @@ -812,13 +812,6 @@ user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" -[[publisher.windows-core]] -version = "0.56.0" -when = "2024-04-12" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - [[publisher.windows-core]] version = "0.61.2" when = "2025-05-19" @@ -833,13 +826,6 @@ user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" -[[publisher.windows-implement]] -version = "0.56.0" -when = "2024-04-12" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - [[publisher.windows-implement]] version = "0.60.2" when = "2025-10-06" @@ -847,13 +833,6 @@ user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" -[[publisher.windows-interface]] -version = "0.56.0" -when = "2024-04-12" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - [[publisher.windows-interface]] version = "0.59.3" when = "2025-10-06" @@ -868,13 +847,6 @@ user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" -[[publisher.windows-result]] -version = "0.1.2" -when = "2024-06-07" -user-id = 64539 -user-login = "kennykerr" -user-name = "Kenny Kerr" - [[publisher.windows-result]] version = "0.3.4" when = "2025-05-19" @@ -1363,6 +1335,12 @@ criteria = "safe-to-deploy" delta = "0.46.0 -> 0.50.1" notes = "Lots of stylistic/rust-related chanegs, plus new features, but nothing out of the ordrinary." +[[audits.bytecode-alliance.audits.parking]] +who = "Chris Fallin " +criteria = "safe-to-deploy" +version = "2.2.1" +notes = "forbid-unsafe crate with straightforward imports." + [[audits.bytecode-alliance.audits.percent-encoding]] who = "Alex Crichton " criteria = "safe-to-deploy"