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