Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::dto::openai::Request;
/// # Transformation Rules
///
/// - If `reasoning.enabled == Some(false)` → use "none" (disables reasoning)
/// - If `reasoning.effort` is set (low/medium/high) → use that value
/// - If `reasoning.effort` is set → use that value verbatim
/// - If `reasoning.max_tokens` is set (thinking budget) → convert to effort:
/// - 0-1024 → "low"
/// - 1025-8192 → "medium"
Expand Down Expand Up @@ -144,6 +144,22 @@ mod tests {
assert_eq!(actual.reasoning, None);
}

#[test]
fn test_reasoning_with_effort_xhigh() {
let fixture = Request::default().reasoning(ReasoningConfig {
enabled: Some(true),
effort: Some(Effort::Xhigh),
max_tokens: None,
exclude: None,
});

let mut transformer = SetReasoningEffort;
let actual = transformer.transform(fixture);

assert_eq!(actual.reasoning_effort, Some("xhigh".to_string()));
assert_eq!(actual.reasoning, None);
}

#[test]
fn test_reasoning_none_doesnt_add_effort() {
let fixture = Request::default();
Expand Down
7 changes: 7 additions & 0 deletions crates/forge_domain/src/agent_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ pub struct ReasoningConfig {
#[strum(serialize_all = "lowercase")]
pub enum Effort {
High,
Xhigh,
Medium,
Low,
}
Expand Down Expand Up @@ -315,6 +316,12 @@ mod tests {
assert_eq!(Effort::from(100000), Effort::High);
}

#[test]
fn test_effort_xhigh_serializes_as_lowercase() {
assert_eq!(Effort::Xhigh.to_string(), "xhigh");
assert_eq!(serde_json::to_value(Effort::Xhigh).unwrap(), json!("xhigh"));
}

#[test]
fn test_merge_model() {
// Base has a value, should not be overwritten
Expand Down
4 changes: 4 additions & 0 deletions crates/forge_domain/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ impl ProviderId {
pub const IO_INTELLIGENCE: ProviderId = ProviderId(Cow::Borrowed("io_intelligence"));
pub const BEDROCK: ProviderId = ProviderId(Cow::Borrowed("bedrock"));
pub const CODEX: ProviderId = ProviderId(Cow::Borrowed("codex"));
pub const MINIMAX: ProviderId = ProviderId(Cow::Borrowed("minimax"));

/// Returns all built-in provider IDs
///
Expand Down Expand Up @@ -96,6 +97,7 @@ impl ProviderId {
ProviderId::IO_INTELLIGENCE,
ProviderId::BEDROCK,
ProviderId::CODEX,
ProviderId::MINIMAX,
]
}

Expand All @@ -118,6 +120,7 @@ impl ProviderId {
"openai_responses_compatible" => "OpenAIResponsesCompatible".to_string(),
"io_intelligence" => "IOIntelligence".to_string(),
"codex" => "Codex".to_string(),
"minimax" => "MiniMax".to_string(),
_ => {
// For other providers, use UpperCamelCase conversion
use convert_case::{Case, Casing};
Expand Down Expand Up @@ -159,6 +162,7 @@ impl std::str::FromStr for ProviderId {
"forge_services" => ProviderId::FORGE_SERVICES,
"io_intelligence" => ProviderId::IO_INTELLIGENCE,
"codex" => ProviderId::CODEX,
"minimax" => ProviderId::MINIMAX,
// For custom providers, use Cow::Owned to avoid memory leaks
custom => ProviderId(Cow::Owned(custom.to_string())),
};
Expand Down
3 changes: 3 additions & 0 deletions crates/forge_repo/src/conversation/conversation_record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,7 @@ impl From<ToolChoiceRecord> for forge_domain::ToolChoice {
#[serde(rename_all = "lowercase")]
pub(super) enum EffortRecord {
High,
Xhigh,
Medium,
Low,
}
Expand All @@ -655,6 +656,7 @@ impl From<&forge_domain::Effort> for EffortRecord {
fn from(effort: &forge_domain::Effort) -> Self {
match effort {
forge_domain::Effort::High => Self::High,
forge_domain::Effort::Xhigh => Self::Xhigh,
forge_domain::Effort::Medium => Self::Medium,
forge_domain::Effort::Low => Self::Low,
}
Expand All @@ -665,6 +667,7 @@ impl From<EffortRecord> for forge_domain::Effort {
fn from(record: EffortRecord) -> Self {
match record {
EffortRecord::High => Self::High,
EffortRecord::Xhigh => Self::Xhigh,
EffortRecord::Medium => Self::Medium,
EffortRecord::Low => Self::Low,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ use forge_domain::Transformer;
/// - `include` always contains `reasoning.encrypted_content` for stateless
/// reasoning continuity.
/// - `text.verbosity` is forced to `Low` for concise output.
/// - `reasoning.effort` is forced to `High` and `reasoning.summary` to `Auto`.
/// - `reasoning.summary` is forced to `Auto`.
/// - `reasoning.effort` defaults to `High`, but preserves explicit `Xhigh`.
pub struct CodexTransformer;

impl Transformer for CodexTransformer {
Expand All @@ -28,8 +29,17 @@ impl Transformer for CodexTransformer {
includes.push(oai::IncludeEnum::ReasoningEncryptedContent);
}

// Force text verbosity to Low for concise codex output
let text = request.text.get_or_insert(oai::ResponseTextParam {
format: oai::TextResponseFormatConfiguration::Text,
verbosity: None,
});
text.verbosity = Some(oai::Verbosity::Low);

if let Some(reasoning) = request.reasoning.as_mut() {
reasoning.effort = Some(oai::ReasoningEffort::High);
if reasoning.effort != Some(oai::ReasoningEffort::Xhigh) {
reasoning.effort = Some(oai::ReasoningEffort::High);
}
reasoning.summary = Some(oai::ReasoningSummary::Auto);
}

Expand Down Expand Up @@ -142,6 +152,58 @@ mod tests {
);
}

#[test]
fn test_codex_transformer_preserves_xhigh_reasoning_effort() {
let reasoning = oai::Reasoning {
effort: Some(oai::ReasoningEffort::Xhigh),
summary: Some(oai::ReasoningSummary::Detailed),
};

let mut fixture = fixture();
fixture.reasoning = Some(reasoning);
let mut transformer = CodexTransformer;
let actual = transformer.transform(fixture);

assert_eq!(
actual.reasoning.as_ref().and_then(|r| r.effort.clone()),
Some(oai::ReasoningEffort::Xhigh)
);
assert_eq!(
actual.reasoning.as_ref().and_then(|r| r.summary),
Some(oai::ReasoningSummary::Auto)
);
}

#[test]
fn test_codex_transformer_sets_text_verbosity_low() {
let fixture = fixture();
let mut transformer = CodexTransformer;
let actual = transformer.transform(fixture);

let expected = Some(oai::Verbosity::Low);
assert_eq!(
actual.text.as_ref().and_then(|t| t.verbosity.clone()),
expected
);
}

#[test]
fn test_codex_transformer_overrides_text_verbosity_to_low() {
let mut fixture = fixture();
fixture.text = Some(oai::ResponseTextParam {
format: oai::TextResponseFormatConfiguration::Text,
verbosity: Some(oai::Verbosity::High),
});
let mut transformer = CodexTransformer;
let actual = transformer.transform(fixture);

let expected = Some(oai::Verbosity::Low);
assert_eq!(
actual.text.as_ref().and_then(|t| t.verbosity.clone()),
expected
);
}

#[test]
fn test_codex_transformer_no_reasoning_unchanged() {
let fixture = fixture();
Expand Down
20 changes: 20 additions & 0 deletions crates/forge_repo/src/provider/openai_responses/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ impl FromDomain<ReasoningConfig> for oai::Reasoning {
if let Some(effort) = config.effort {
let oai_effort = match effort {
Effort::High => oai::ReasoningEffort::High,
Effort::Xhigh => oai::ReasoningEffort::Xhigh,
Effort::Medium => oai::ReasoningEffort::Medium,
Effort::Low => oai::ReasoningEffort::Low,
};
Expand Down Expand Up @@ -417,6 +418,25 @@ mod tests {
Ok(())
}

#[test]
fn test_reasoning_config_conversion_with_xhigh_effort() -> anyhow::Result<()> {
use forge_domain::{Effort, ReasoningConfig};

let fixture = ReasoningConfig {
effort: Some(Effort::Xhigh),
max_tokens: None,
exclude: None,
enabled: None,
};

let actual = oai::Reasoning::from_domain(fixture)?;

assert_eq!(actual.effort, Some(oai::ReasoningEffort::Xhigh));
assert!(actual.summary.is_some());

Ok(())
}

#[test]
fn test_codex_request_with_reasoning_config() -> anyhow::Result<()> {
use forge_domain::{Effort, ReasoningConfig};
Expand Down
85 changes: 69 additions & 16 deletions crates/forge_repo/src/provider/provider.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,67 @@
"response_type": "OpenAI",
"url": "https://api.githubcopilot.com/chat/completions",
"models": "https://api.githubcopilot.com/models",
"auth_methods": [
"auth_methods": ["api_key"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is wrong githubcopilot should not change in adding another provider

},
{
"id": "minimax",
"api_key_vars": "MINIMAX_API_KEY",
"url_param_vars": [],
"response_type": "Anthropic",
"url": "https://api.minimax.io/anthropic/v1/messages",
"models": [
{
"oauth_device": {
"auth_url": "https://github.com/login/device/code",
"token_url": "https://github.com/login/oauth/access_token",
"client_id": "Iv1.b507a08c87ecfe98",
"scopes": ["read:user"],
"use_pkce": false,
"token_refresh_url": "https://api.github.com/copilot_internal/v2/token",
"custom_headers": {
"User-Agent": "GitHubCopilotChat/0.26.7",
"Accept": "application/json",
"editor-version": "vscode/1.99.3",
"editor-plugin-version": "copilot-chat/0.26.7"
}
}
"id": "MiniMax-M2",
"name": "MiniMax M2",
"description": "MiniMax M2 agentic model with advanced reasoning",
"context_length": 204800,
"tools_supported": true,
"supports_parallel_tool_calls": true,
"supports_reasoning": true,
"input_modalities": ["text"]
Comment on lines +27 to +44

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical: GitHub Copilot OAuth authentication removed

The GitHub Copilot provider's OAuth device flow configuration has been completely deleted and replaced with just ["api_key"]. This change:

  1. Removes the entire oauth_device authentication method including auth URLs, client ID, scopes, and custom headers
  2. Was not mentioned in the PR description (which only discusses adding MiniMax support)
  3. Will break authentication for all users relying on GitHub Copilot's OAuth device flow

Fix: Restore the GitHub Copilot OAuth configuration. The intended change should only add the MiniMax provider block after the GitHub Copilot provider, not modify it:

"auth_methods": [
  {
    "oauth_device": {
      "auth_url": "https://github.com/login/device/code",
      "token_url": "https://github.com/login/oauth/access_token",
      "client_id": "Iv1.b507a08c87ecfe98",
      "scopes": ["read:user"],
      "use_pkce": false,
      "token_refresh_url": "https://api.github.com/copilot_internal/v2/token",
      "custom_headers": {
        "User-Agent": "GitHubCopilotChat/0.26.7",
        "Accept": "application/json",
        "editor-version": "vscode/1.99.3",
        "editor-plugin-version": "copilot-chat/0.26.7"
      }
    }
  }
]
Suggested change
"auth_methods": ["api_key"]
},
{
"id": "minimax",
"api_key_vars": "MINIMAX_API_KEY",
"url_param_vars": [],
"response_type": "Anthropic",
"url": "https://api.minimax.io/anthropic/v1/messages",
"models": [
{
"oauth_device": {
"auth_url": "https://github.com/login/device/code",
"token_url": "https://github.com/login/oauth/access_token",
"client_id": "Iv1.b507a08c87ecfe98",
"scopes": ["read:user"],
"use_pkce": false,
"token_refresh_url": "https://api.github.com/copilot_internal/v2/token",
"custom_headers": {
"User-Agent": "GitHubCopilotChat/0.26.7",
"Accept": "application/json",
"editor-version": "vscode/1.99.3",
"editor-plugin-version": "copilot-chat/0.26.7"
}
}
"id": "MiniMax-M2",
"name": "MiniMax M2",
"description": "MiniMax M2 agentic model with advanced reasoning",
"context_length": 204800,
"tools_supported": true,
"supports_parallel_tool_calls": true,
"supports_reasoning": true,
"input_modalities": ["text"]
"auth_methods": [
{
"oauth_device": {
"auth_url": "https://github.com/login/device/code",
"token_url": "https://github.com/login/oauth/access_token",
"client_id": "Iv1.b507a08c87ecfe98",
"scopes": ["read:user"],
"use_pkce": false,
"token_refresh_url": "https://api.github.com/copilot_internal/v2/token",
"custom_headers": {
"User-Agent": "GitHubCopilotChat/0.26.7",
"Accept": "application/json",
"editor-version": "vscode/1.99.3",
"editor-plugin-version": "copilot-chat/0.26.7"
}
}
}
]
},
{
"id": "minimax",
"api_key_vars": "MINIMAX_API_KEY",
"url_param_vars": [],
"response_type": "Anthropic",
"url": "https://api.minimax.io/anthropic/v1/messages",
"models": [
{
"id": "MiniMax-M2",
"name": "MiniMax M2",
"description": "MiniMax M2 agentic model with advanced reasoning",
"context_length": 204800,
"tools_supported": true,
"supports_parallel_tool_calls": true,
"supports_reasoning": true,
"input_modalities": ["text"]

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

},
{
"id": "MiniMax-M2.5",
"name": "MiniMax M2.5",
"description": "MiniMax M2.5 peak performance model for coding and complex tasks",
"context_length": 204800,
"tools_supported": true,
"supports_parallel_tool_calls": true,
"supports_reasoning": true,
"input_modalities": ["text"]
},
{
"id": "MiniMax-M2.5-highspeed",
"name": "MiniMax M2.5 Highspeed",
"description": "MiniMax M2.5 highspeed variant for faster responses",
"context_length": 204800,
"tools_supported": true,
"supports_parallel_tool_calls": true,
"supports_reasoning": true,
"input_modalities": ["text"]
},
{
"id": "MiniMax-M2.1",
"name": "MiniMax M2.1",
"description": "MiniMax M2.1 with powerful multi-language programming capabilities",
"context_length": 204800,
"tools_supported": true,
"supports_parallel_tool_calls": true,
"supports_reasoning": true,
"input_modalities": ["text"]
},
{
"id": "MiniMax-M2.1-highspeed",
"name": "MiniMax M2.1 Highspeed",
"description": "MiniMax M2.1 highspeed variant for faster responses",
"context_length": 204800,
"tools_supported": true,
"supports_parallel_tool_calls": true,
"supports_reasoning": true,
"input_modalities": ["text"]
}
]
],
"auth_methods": ["api_key"]
},
{
"id": "open_router",
Expand Down Expand Up @@ -504,6 +547,16 @@
"supports_parallel_tool_calls": true,
"supports_reasoning": true,
"input_modalities": ["text", "image"]
},
{
"id": "chatgpt-5.4-xhigh",
"name": "ChatGPT 5.4 XHigh",
"description": "ChatGPT 5.4 variant tuned for the highest reasoning effort",
"context_length": 1000000,
"tools_supported": true,
"supports_parallel_tool_calls": true,
"supports_reasoning": true,
"input_modalities": ["text", "image"]
}
],
"auth_methods": ["api_key"]
Expand Down
Loading
Loading