You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/09/23 20:57:44 UTC

(maka) branch feat/runtime-host-rust updated: fix(providers): preserve account reasoning policy through Responses

This is an automated email from the ASF dual-hosted git repository.

M4n5ter pushed a commit to branch feat/runtime-host-rust
in repository https://gitbox.apache.org/repos/asf/maka.git


The following commit(s) were added to refs/heads/feat/runtime-host-rust by this push:
     new 9c651da46a fix(providers): preserve account reasoning policy through Responses
9c651da46a is described below

commit 9c651da46a28a90ca366a5d04c03d3b9eeee2aa4
Author: Wang <m4...@gmail.com>
AuthorDate: Thu Sep 24 04:57:39 2026 +0800

    fix(providers): preserve account reasoning policy through Responses
    
    Converge subscription inventory decoding in the provider, retain explicit capabilities and model policy, and keep Responses encoding free of implicit summary defaults.
    
    Generated-by: OpenAI Codex
---
 crates/model/src/connection/models.rs              |   5 +-
 crates/model/src/connection/models/subscription.rs | 119 +-----------
 crates/providers/src/codex.rs                      |   2 +
 crates/providers/src/codex/inventory.rs            | 199 +++++++++++++++++++++
 crates/providers/src/codex/provider.rs             |  90 +++-------
 crates/providers/tests/codex.rs                    |  25 ++-
 crates/providers/tests/codex/inventory.rs          | 145 +++++++++++++++
 crates/responses/src/request.rs                    |   7 -
 packages/plugin-sdk/src/providers.ts               |   5 +-
 9 files changed, 396 insertions(+), 201 deletions(-)

diff --git a/crates/model/src/connection/models.rs b/crates/model/src/connection/models.rs
index c62c8b115b..b13d78b445 100644
--- a/crates/model/src/connection/models.rs
+++ b/crates/model/src/connection/models.rs
@@ -27,6 +27,10 @@ mod subscription;
 
 /// Decode the native providers' `data` envelope into canonical catalog rows.
 pub(super) fn decode(body: &[u8], kind: DiscoveryKind) -> Result<Vec<ModelInfo>, Failure> {
+    if matches!(kind, DiscoveryKind::Codex) {
+        return maka_providers::codex::decode_model_inventory(body)
+            .map_err(|_| Failure::InvalidResponse);
+    }
     let text = String::from_utf8_lossy(body);
     let text = text.strip_prefix('\u{feff}').unwrap_or(&text);
     let root: Value = serde_json::from_str(text).map_err(|_| Failure::InvalidResponse)?;
@@ -34,7 +38,6 @@ pub(super) fn decode(body: &[u8], kind: DiscoveryKind) -> Result<Vec<ModelInfo>,
         return Err(Failure::InvalidResponse);
     }
     let rows = match kind {
-        DiscoveryKind::Codex => subscription::codex(&root)?,
         DiscoveryKind::Copilot => subscription::copilot(&root)?,
         _ => object_array(root.get("data"))?
             .iter()
diff --git a/crates/model/src/connection/models/subscription.rs b/crates/model/src/connection/models/subscription.rs
index 114f559588..bd8e616ac0 100644
--- a/crates/model/src/connection/models/subscription.rs
+++ b/crates/model/src/connection/models/subscription.rs
@@ -17,83 +17,9 @@
  * under the License.
  */
 
-use super::{
-    Failure, copy_bool, copy_number, js_truthy, js_whitespace, object_array, optional_array,
-    token_limit,
-};
+use super::{Failure, copy_number, js_truthy, object_array, optional_array, token_limit};
 use serde_json::{Map, Value, json};
 
-pub(super) fn codex(root: &Value) -> Result<Vec<Value>, Failure> {
-    // Subscription inventories require their envelope; a missing field is not an empty list.
-    let rows = root.get("models").ok_or(Failure::InvalidResponse)?;
-    let mut rows: Vec<_> = object_array(Some(rows))?
-        .iter()
-        .filter(|row| {
-            row["slug"]
-                .as_str()
-                .is_some_and(|id| !id.trim_matches(js_whitespace).is_empty())
-                && !row["visibility"].as_str().is_some_and(|s| {
-                    matches!(
-                        s.trim_matches(js_whitespace).to_ascii_lowercase().as_str(),
-                        "hide" | "hidden"
-                    )
-                })
-        })
-        .collect();
-    let priority = |row: &Value| row["priority"].as_f64().unwrap_or(10_000.0);
-    rows.sort_by(|a, b| {
-        priority(a)
-            .partial_cmp(&priority(b))
-            .expect("finite JSON numbers")
-    });
-    rows.into_iter()
-        .map(|row| {
-            let mut model = Map::new();
-            model.insert("id".into(), row["slug"].clone());
-            if let Some(name) = row.get("display_name").and_then(Value::as_str) {
-                model.insert("displayName".into(), name.into());
-            }
-            if row["context_window"].as_f64().is_some_and(|n| n > 0.0) {
-                copy_number(row.get("context_window"), &mut model, "contextWindow");
-            }
-            let mut capabilities = Map::new();
-            if let Some(levels) = row.get("supported_reasoning_levels") {
-                let advertised = object_array(Some(levels))?;
-                let levels: Vec<_> = ["off", "minimal", "low", "medium", "high", "xhigh", "max"]
-                    .into_iter()
-                    .filter(|level| {
-                        advertised.iter().any(|entry| {
-                            entry["effort"] == *level
-                                || (*level == "off" && entry["effort"] == "none")
-                        })
-                    })
-                    .collect();
-                capabilities.insert("reasoning".into(), (!levels.is_empty()).into());
-                model.insert("thinkingLevels".into(), json!(levels));
-            }
-            copy_bool(
-                row.get("supports_reasoning_summary_parameter")
-                    .or_else(|| row.get("supports_reasoning_summaries")),
-                &mut model,
-                "supportsReasoningSummary",
-            );
-            if let Some(input) = row.get("input_modalities") {
-                let input = optional_array(Some(input))?;
-                capabilities.insert("vision".into(), input.iter().any(|v| v == "image").into());
-            }
-            copy_bool(
-                row.get("supports_parallel_tool_calls"),
-                &mut capabilities,
-                "parallelToolCalls",
-            );
-            if !capabilities.is_empty() {
-                model.insert("capabilities".into(), Value::Object(capabilities));
-            }
-            Ok(Value::Object(model))
-        })
-        .collect()
-}
-
 pub(super) fn copilot(root: &Value) -> Result<Vec<Value>, Failure> {
     let rows = object_array(Some(root.get("data").ok_or(Failure::InvalidResponse)?))?;
     let mut models = Vec::new();
@@ -187,46 +113,3 @@ fn blocked_by_policy(row: &Value) -> bool {
             Some("disabled" | "unconfigured")
         )
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use maka_runtime::{configuration::ModelInfo, execution::ThinkingLevel};
-
-    #[test]
-    fn codex_preserves_advertised_reasoning_without_guessing_unknown_efforts() {
-        let source = json!({"models":[{
-            "slug":"future-codex", "display_name":"Future Codex", "context_window":272000,
-            "supported_reasoning_levels":[{"effort":"high"},{"effort":"low"},
-                {"effort":"low"},{"effort":"future-level"}],
-            "supports_reasoning_summaries":true, "supports_reasoning_summary_parameter":false,
-            "input_modalities":["text","image"], "supports_parallel_tool_calls":true
-        }, {"slug":"plain", "supported_reasoning_levels":[]}, {"slug":"unknown"}]});
-        let rows = codex(&source).unwrap();
-        let model: ModelInfo = serde_json::from_value(rows[0].clone()).unwrap();
-        model.validate().unwrap();
-        assert_eq!(
-            model.thinking_levels,
-            Some(vec![ThinkingLevel::Low, ThinkingLevel::High])
-        );
-        assert_eq!(model.supports_reasoning_summary, Some(false));
-        assert_eq!(model.capabilities.unwrap().vision, Some(true));
-        assert_eq!(model.display_name.as_deref(), Some("Future Codex"));
-        assert_eq!(rows[1]["thinkingLevels"], json!([]));
-        assert!(rows[2].get("thinkingLevels").is_none());
-        let mut duplicate = rows[0].clone();
-        duplicate["thinkingLevels"] = json!(["low", "low"]);
-        assert!(
-            serde_json::from_value::<ModelInfo>(duplicate)
-                .unwrap()
-                .validate()
-                .is_err()
-        );
-        for invalid in [Value::Null, json!([{"effort":"invalid"}, 1])] {
-            assert!(
-                codex(&json!({"models":[{"slug":"bad","supported_reasoning_levels":invalid}]}))
-                    .is_err()
-            );
-        }
-    }
-}
diff --git a/crates/providers/src/codex.rs b/crates/providers/src/codex.rs
index d0da4665b8..4b7efcc6de 100644
--- a/crates/providers/src/codex.rs
+++ b/crates/providers/src/codex.rs
@@ -27,8 +27,10 @@ use maka_plugins::{
 use serde_json::{Value, json};
 use std::{collections::BTreeMap, sync::Arc};
 use tokio_util::sync::CancellationToken;
+mod inventory;
 mod login;
 mod provider;
+pub use inventory::decode_model_inventory;
 use provider::configuration;
 
 pub const ID: &str = "maka.codex";
diff --git a/crates/providers/src/codex/inventory.rs b/crates/providers/src/codex/inventory.rs
new file mode 100644
index 0000000000..1bd80f92ff
--- /dev/null
+++ b/crates/providers/src/codex/inventory.rs
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use maka_plugins::provider::Error;
+use maka_runtime::{
+    configuration::{ModelCapabilities, ModelInfo},
+    execution::ThinkingLevel,
+};
+use serde::{Deserialize, Deserializer};
+use std::collections::HashSet;
+
+#[derive(Deserialize)]
+struct Inventory {
+    models: Vec<Row>,
+}
+
+#[derive(Deserialize)]
+struct Row {
+    slug: Option<String>,
+    visibility: Option<String>,
+    priority: Option<f64>,
+    display_name: Option<String>,
+    context_window: Option<f64>,
+    #[serde(default, deserialize_with = "present")]
+    supported_reasoning_levels: Option<Vec<Reasoning>>,
+    supports_reasoning_summary_parameter: Option<bool>,
+    supports_reasoning_summaries: Option<bool>,
+    #[serde(default, deserialize_with = "present")]
+    input_modalities: Option<Vec<String>>,
+    supports_parallel_tool_calls: Option<bool>,
+}
+
+#[derive(Deserialize)]
+struct Reasoning {
+    effort: String,
+}
+
+// An omitted declaration is unknown; null is not an advertised empty list.
+fn present<'de, D: Deserializer<'de>, T: Deserialize<'de>>(d: D) -> Result<Option<T>, D::Error> {
+    T::deserialize(d).map(Some)
+}
+
+/// Decode account facts once for discovery and provider request preparation.
+pub fn decode_model_inventory(bytes: &[u8]) -> Result<Vec<ModelInfo>, Error> {
+    if bytes.len() > 4 * 1024 * 1024 {
+        return Err(invalid());
+    }
+    let text = String::from_utf8_lossy(bytes);
+    let text = text.strip_prefix('\u{feff}').unwrap_or(&text);
+    let mut inventory: Inventory = serde_json::from_str(text).map_err(|_| invalid())?;
+    inventory.models.sort_by(|a, b| {
+        a.priority
+            .unwrap_or(10_000.0)
+            .total_cmp(&b.priority.unwrap_or(10_000.0))
+    });
+    let mut seen = HashSet::new();
+    let mut models = Vec::new();
+    for row in inventory.models {
+        let Some(id) = row.slug.as_deref().map(|id| id.trim_matches(whitespace)) else {
+            continue;
+        };
+        if id.is_empty()
+            || id.encode_utf16().count() > 512
+            || id.chars().any(|c| c <= '\u{1f}' || c == '\u{7f}')
+            || row.visibility.as_deref().is_some_and(|value| {
+                matches!(
+                    value.trim_matches(whitespace).to_ascii_lowercase().as_str(),
+                    "hide" | "hidden"
+                )
+            })
+            || !seen.insert(id.to_owned())
+        {
+            continue;
+        }
+        let mut info = ModelInfo::new(id);
+        info.display_name = row.display_name;
+        info.context_window = row
+            .context_window
+            .filter(|n| (1.0..=9_007_199_254_740_991.0).contains(n) && n.fract() == 0.0)
+            .map(|n| n as u64);
+        if let Some(advertised) = row.supported_reasoning_levels {
+            use ThinkingLevel::*;
+            info.thinking_levels = Some(
+                [
+                    (Off, "none"),
+                    (Minimal, "minimal"),
+                    (Low, "low"),
+                    (Medium, "medium"),
+                    (High, "high"),
+                    (Xhigh, "xhigh"),
+                    (Max, "max"),
+                ]
+                .into_iter()
+                .filter_map(|(level, name)| {
+                    advertised
+                        .iter()
+                        .any(|entry| {
+                            entry.effort == name || (level == Off && entry.effort == "off")
+                        })
+                        .then_some(level)
+                })
+                .collect(),
+            );
+        }
+        info.supports_reasoning_summary = row
+            .supports_reasoning_summary_parameter
+            .or(row.supports_reasoning_summaries);
+        let capabilities = ModelCapabilities {
+            reasoning: info
+                .thinking_levels
+                .as_ref()
+                .map(|levels| !levels.is_empty()),
+            vision: row
+                .input_modalities
+                .as_ref()
+                .map(|input| input.iter().any(|kind| kind == "image")),
+            parallel_tool_calls: row.supports_parallel_tool_calls,
+            ..Default::default()
+        };
+        if capabilities != ModelCapabilities::default() {
+            info.capabilities = Some(capabilities);
+        }
+        info.validate().map_err(|_| invalid())?;
+        models.push(info);
+        if models.len() > 2048 {
+            return Err(invalid());
+        }
+    }
+    Ok(models)
+}
+
+fn invalid() -> Error {
+    Error::Invalid("invalid subscription inventory".into())
+}
+
+fn whitespace(c: char) -> bool {
+    matches!(c, '\u{9}'..='\u{d}' | '\u{20}' | '\u{a0}' | '\u{1680}'
+        | '\u{2000}'..='\u{200a}' | '\u{2028}' | '\u{2029}' | '\u{202f}'
+        | '\u{205f}' | '\u{3000}' | '\u{feff}')
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use serde_json::{Value, json};
+
+    #[test]
+    fn account_facts_preserve_absent_empty_and_explicit_negative_declarations() {
+        let source = json!({"models":[{
+            "slug":"future-codex", "display_name":"Future Codex", "context_window":272000,
+            "supported_reasoning_levels":[{"effort":"high"},{"effort":"low"},
+                {"effort":"low"},{"effort":"future-level"}],
+            "supports_reasoning_summaries":true, "supports_reasoning_summary_parameter":false,
+            "input_modalities":["text","image"], "supports_parallel_tool_calls":false
+        }, {"slug":"plain", "supported_reasoning_levels":[]}, {"slug":"unknown"}]});
+        let rows = decode_model_inventory(&serde_json::to_vec(&source).unwrap()).unwrap();
+        assert_eq!(
+            rows[0].thinking_levels,
+            Some(vec![ThinkingLevel::Low, ThinkingLevel::High])
+        );
+        assert_eq!(rows[0].supports_reasoning_summary, Some(false));
+        assert_eq!(rows[0].capabilities.unwrap().vision, Some(true));
+        assert_eq!(
+            rows[0].capabilities.unwrap().parallel_tool_calls,
+            Some(false)
+        );
+        assert_eq!(rows[0].display_name.as_deref(), Some("Future Codex"));
+        assert_eq!(rows[1].thinking_levels, Some(vec![]));
+        assert_eq!(rows[1].capabilities.unwrap().reasoning, Some(false));
+        assert!(rows[2].thinking_levels.is_none());
+        for invalid in [Value::Null, json!([{"effort":"invalid"}, 1])] {
+            assert!(
+                decode_model_inventory(
+                    &serde_json::to_vec(&json!({
+                        "models":[{"slug":"bad","supported_reasoning_levels":invalid}]
+                    }))
+                    .unwrap()
+                )
+                .is_err()
+            );
+        }
+    }
+}
diff --git a/crates/providers/src/codex/provider.rs b/crates/providers/src/codex/provider.rs
index aa2b047810..de99a6837d 100644
--- a/crates/providers/src/codex/provider.rs
+++ b/crates/providers/src/codex/provider.rs
@@ -28,7 +28,7 @@ use maka_plugins::{
     },
 };
 use maka_runtime::{
-    configuration::{ApiProtocol, ModelCapabilities, ModelInfo},
+    configuration::{ApiProtocol, ModelInfo},
     execution::ThinkingLevel,
 };
 use serde::{Deserialize, Serialize};
@@ -98,17 +98,15 @@ impl Provider for Codex {
                 .overrides
                 .as_ref()
                 .and_then(|o| o.thinking_levels.clone())
-                .unwrap_or_else(|| {
-                    vec![
-                        ThinkingLevel::Low,
-                        ThinkingLevel::Medium,
-                        ThinkingLevel::High,
-                        ThinkingLevel::Xhigh,
-                    ]
-                });
-            if request.thinking_level.is_some_and(|level| {
-                level != ThinkingLevel::Off && !thinking_levels.contains(&level)
-            }) {
+                .or_else(|| info.thinking_levels.clone())
+                .unwrap_or_default();
+            let level = request.thinking_level.or_else(|| {
+                request
+                    .overrides
+                    .as_ref()
+                    .and_then(|o| o.default_thinking_level)
+            });
+            if level.is_some_and(|level| !thinking_levels.contains(&level)) {
                 return Err(Error::Invalid(
                     "unsupported subscription thinking level".into(),
                 ));
@@ -118,19 +116,22 @@ impl Provider for Codex {
                 .and_then(|c| c.parallel_tool_calls)
                 .unwrap_or(true);
             let mut options = json!({"openai":{"store":false,"parallelToolCalls":parallel}});
-            if let Some(level) = request
-                .thinking_level
-                .or(Some(ThinkingLevel::Medium).filter(|l| thinking_levels.contains(l)))
-            {
+            if let Some(level) = level {
                 options["openai"]["reasoningEffort"] = match level {
                     ThinkingLevel::Off => json!("none"),
                     level => {
                         serde_json::to_value(level).map_err(|e| Error::Invalid(e.to_string()))?
                     }
                 };
-                if level != ThinkingLevel::Off {
-                    options["openai"]["reasoningSummary"] = json!("auto");
-                }
+            }
+            let summary = info
+                .supports_reasoning_summary
+                .unwrap_or(info.capabilities.and_then(|c| c.reasoning) != Some(false));
+            if summary && level != Some(ThinkingLevel::Off) {
+                options["openai"]["reasoningSummary"] = json!("auto");
+            }
+            if summary || level.is_some() {
+                options["openai"]["forceReasoning"] = json!(true);
             }
             let model = Model {
                 adapter: request
@@ -228,7 +229,7 @@ impl Provider for Codex {
                     }
                     bytes.extend_from_slice(&chunk);
                 }
-                decode(&bytes)
+                super::decode_model_inventory(&bytes)
             }
             .await;
             response.body.cancel();
@@ -241,52 +242,3 @@ impl Provider for Codex {
         })
     }
 }
-
-fn decode(bytes: &[u8]) -> Result<Vec<ModelInfo>, Error> {
-    #[derive(Deserialize)]
-    struct Inventory {
-        models: Vec<Row>,
-    }
-    #[derive(Deserialize)]
-    struct Row {
-        slug: String,
-        visibility: Option<String>,
-        context_window: Option<u64>,
-        #[serde(default = "priority")]
-        priority: f64,
-    }
-    fn priority() -> f64 {
-        10_000.0
-    }
-    let mut inventory: Inventory = serde_json::from_slice(bytes)
-        .map_err(|_| Error::Invalid("invalid subscription inventory".into()))?;
-    inventory
-        .models
-        .sort_by(|a, b| a.priority.total_cmp(&b.priority));
-    inventory
-        .models
-        .into_iter()
-        .filter(|r| {
-            !r.slug.trim().is_empty()
-                && !r
-                    .visibility
-                    .as_ref()
-                    .is_some_and(|v| matches!(v.trim().to_lowercase().as_str(), "hide" | "hidden"))
-        })
-        .map(|row| {
-            let mut info = ModelInfo::new(row.slug);
-            info.context_window = row.context_window.filter(|n| *n > 0);
-            info.capabilities = Some(ModelCapabilities {
-                chat: Some(true),
-                vision: Some(true),
-                reasoning: Some(true),
-                function_calling: Some(true),
-                parallel_tool_calls: Some(true),
-                web_search: Some(true),
-                ..Default::default()
-            });
-            info.validate().map_err(Error::Invalid)?;
-            Ok(info)
-        })
-        .collect()
-}
diff --git a/crates/providers/tests/codex.rs b/crates/providers/tests/codex.rs
index 8a1dddb7f0..595470104b 100644
--- a/crates/providers/tests/codex.rs
+++ b/crates/providers/tests/codex.rs
@@ -24,7 +24,9 @@ use maka_plugins::{
     fiber::Fiber,
     http,
     model::{Connect, Credentials, Error as ModelError, Socket, Transport},
-    provider::{Binding, Connection, Context, Error, authentication::Credential},
+    provider::{
+        Binding, Connection, Context, Error, Provider, Resolve, authentication::Credential,
+    },
 };
 use maka_providers::codex::Codex;
 use serde_json::json;
@@ -32,11 +34,15 @@ use std::sync::{Arc, Mutex};
 use tokio::sync::Notify;
 use tokio_util::sync::CancellationToken;
 
+#[path = "codex/inventory.rs"]
+mod inventory;
+
 struct Exchange {
     started: Notify,
     release: Notify,
     requests: Mutex<Vec<http::Request>>,
     fail: bool,
+    response: serde_json::Value,
 }
 impl Transport for Exchange {
     fn identity(&self) -> u64 {
@@ -51,10 +57,14 @@ impl Transport for Exchange {
                 return Err(ModelError::Adapter("reply lost".into()));
             }
             Ok(http::Response {
-                head: http::Head { status: 200, url: "https://auth.openai.com/oauth/token".into(), headers: vec![] },
-                body: Arc::new(Body(Mutex::new(Some(serde_json::to_vec(&json!({
-                    "access_token": "replacement-access", "refresh_token": "replacement-refresh", "expires_in": 3600,
-                })).unwrap())))),
+                head: http::Head {
+                    status: 200,
+                    url: "https://auth.openai.com/oauth/token".into(),
+                    headers: vec![],
+                },
+                body: Arc::new(Body(Mutex::new(Some(
+                    serde_json::to_vec(&self.response).unwrap(),
+                )))),
             })
         })
     }
@@ -119,6 +129,11 @@ async fn sent_refresh_settles_after_cancellation_and_unknown_reply_is_never_retr
                 release: Notify::new(),
                 requests: Mutex::new(vec![]),
                 fail,
+                response: json!({
+                    "access_token": "replacement-access",
+                    "refresh_token": "replacement-refresh",
+                    "expires_in": 3600,
+                }),
             });
             let cancellation = CancellationToken::new();
             let context = Context {
diff --git a/crates/providers/tests/codex/inventory.rs b/crates/providers/tests/codex/inventory.rs
new file mode 100644
index 0000000000..fc8ff1ab3c
--- /dev/null
+++ b/crates/providers/tests/codex/inventory.rs
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+use super::*;
+
+#[tokio::test]
+async fn discovery_and_preparation_share_account_facts_and_explicit_policy() {
+    use maka_runtime::{
+        configuration::{ModelInfo, ModelOverride},
+        execution::ThinkingLevel::*,
+    };
+    let transport = Arc::new(Exchange {
+        started: Notify::new(),
+        release: Notify::new(),
+        requests: Mutex::new(vec![]),
+        fail: false,
+        response: json!({"models":[{
+            "slug":"future-model", "context_window":272000,
+            "supported_reasoning_levels":[{"effort":"medium"},{"effort":"high"},{"effort":"max"}],
+            "supports_reasoning_summary_parameter":false,
+            "input_modalities":["text"], "supports_parallel_tool_calls":false
+        }]}),
+    });
+    transport.release.notify_one();
+    let provider = Codex::default();
+    let inventory = provider
+        .discover(
+            connection(),
+            Some(credential()),
+            Context {
+                transport: transport.clone(),
+                cancellation: CancellationToken::new(),
+                interaction: None,
+            },
+        )
+        .await
+        .unwrap();
+    assert_eq!(inventory.len(), 1);
+    let request = Resolve {
+        connection: connection(),
+        model: inventory[0].clone(),
+        overrides: None,
+        thinking_level: None,
+    };
+    let model = provider.resolve(request.clone()).await.unwrap();
+    assert_eq!(model.thinking_levels, vec![Medium, High, Max]);
+    assert_eq!(model.info.context_window, Some(272000));
+    assert_eq!(model.info.capabilities.unwrap().vision, Some(false));
+    assert_eq!(
+        model.provider_options["openai"],
+        json!({"store":false,"parallelToolCalls":false})
+    );
+
+    let mut declared = request.clone();
+    declared.overrides = Some(ModelOverride {
+        default_thinking_level: Some(Max),
+        ..Default::default()
+    });
+    let model = provider.resolve(declared.clone()).await.unwrap();
+    assert_eq!(model.provider_options["openai"]["reasoningEffort"], "max");
+    assert!(
+        model.provider_options["openai"]
+            .get("reasoningSummary")
+            .is_none()
+    );
+    assert_eq!(wire(&model)["reasoning"], json!({"effort":"max"}));
+    declared.thinking_level = Some(High);
+    assert_eq!(
+        provider
+            .resolve(declared.clone())
+            .await
+            .unwrap()
+            .provider_options["openai"]["reasoningEffort"],
+        "high"
+    );
+    declared.thinking_level = Some(Low);
+    assert!(matches!(
+        provider.resolve(declared.clone()).await,
+        Err(Error::Invalid(_))
+    ));
+
+    declared.thinking_level = None;
+    declared.overrides = Some(ModelOverride {
+        thinking_levels: Some(vec![Off, Low]),
+        default_thinking_level: Some(Off),
+        ..Default::default()
+    });
+    declared.model.supports_reasoning_summary = Some(true);
+    let model = provider.resolve(declared).await.unwrap();
+    assert_eq!(model.thinking_levels, vec![Off, Low]);
+    assert_eq!(model.provider_options["openai"]["reasoningEffort"], "none");
+    assert!(
+        model.provider_options["openai"]
+            .get("reasoningSummary")
+            .is_none()
+    );
+
+    let model = provider
+        .resolve(Resolve {
+            model: ModelInfo::new("unadvertised"),
+            ..request
+        })
+        .await
+        .unwrap();
+    assert!(model.thinking_levels.is_empty());
+    assert!(
+        model.provider_options["openai"]
+            .get("reasoningEffort")
+            .is_none()
+    );
+    assert_eq!(model.provider_options["openai"]["reasoningSummary"], "auto");
+    assert_eq!(wire(&model)["reasoning"], json!({"summary":"auto"}));
+    let requests = transport.requests.lock().unwrap();
+    assert_eq!(requests.len(), 1);
+    assert!(requests[0].url.ends_with("/models?client_version=1.0.0"));
+}
+
+fn wire(model: &maka_plugins::provider::Model) -> serde_json::Value {
+    maka_responses::request::Request {
+        model: &model.info.id,
+        prompt: &[],
+        tools: &[],
+        options: &model.provider_options,
+        max_output_tokens: None,
+        plaintext: None,
+    }
+    .encode()
+    .unwrap()
+}
diff --git a/crates/responses/src/request.rs b/crates/responses/src/request.rs
index bdb72f3387..72fd73d8f6 100644
--- a/crates/responses/src/request.rs
+++ b/crates/responses/src/request.rs
@@ -310,13 +310,6 @@ impl Request<'_> {
                 reasoning.insert(target.into(), value.clone());
             }
         }
-        if reasoning
-            .get("effort")
-            .is_some_and(|effort| effort != "none")
-            && !reasoning.contains_key("summary")
-        {
-            reasoning.insert("summary".into(), json!("detailed"));
-        }
         if !reasoning.is_empty() {
             object.insert("reasoning".into(), Value::Object(reasoning));
         }
diff --git a/packages/plugin-sdk/src/providers.ts b/packages/plugin-sdk/src/providers.ts
index 2ab9b8afaf..dc652a9119 100644
--- a/packages/plugin-sdk/src/providers.ts
+++ b/packages/plugin-sdk/src/providers.ts
@@ -40,6 +40,8 @@ export interface ModelInfo {
   contextWindow?: number;
   inputLimit?: number;
   maxOutputTokens?: number;
+  thinkingLevels?: readonly ThinkingLevel[];
+  supportsReasoningSummary?: boolean;
   knowledgeCutoff?: string;
   structuredOutput?: boolean;
   lastUpdated?: string;
@@ -51,6 +53,7 @@ export interface ModelOverride {
   codeMode?: boolean;
   applyPatch?: boolean;
   thinkingLevels?: readonly ThinkingLevel[];
+  defaultThinkingLevel?: ThinkingLevel;
   vision?: boolean;
   contextWindow?: number;
   compactionThreshold?: number;
@@ -62,7 +65,7 @@ export interface ModelOverride {
   knowledgeCutoff?: string;
   capabilities?: Omit<ModelCapabilities, 'vision'>;
   modalities?: ModelInfo['modalities'];
-  serviceTier?: 'auto' | 'default' | 'flex' | 'priority';
+  serviceTier?: 'fast';
 }
 export interface ProviderIdentity {
   packageId: string;


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org