Compare commits

...
2 Commits
Author SHA1 Message Date
zyycnandCodex 13a9df2e07 chore: bump version to 3.0.8
Co-authored-by: Codex <noreply@openai.com>
2026-07-26 05:32:36 +08:00
zyycnandCodex 37a5a6702b fix(xai): restore Codex model capabilities
Read Grok reasoning effort menus from the official catalog, expose xhigh to Codex, and generate command-authenticated client configuration that refreshes remote model metadata without advertising unsupported priority service.

Co-authored-by: Codex <noreply@openai.com>
2026-07-26 05:32:15 +08:00
11 changed files with 231 additions and 38 deletions
@@ -209,6 +209,7 @@ fn codex_model_json(profile: &PublicModelProfile, index: usize) -> Value {
fn reasoning_effort_description(effort: &str) -> &'static str {
match effort {
"none" => "No reasoning",
"minimal" => "Minimal reasoning",
"low" => "Fast responses with lighter reasoning",
"medium" => "Balances speed and reasoning depth for everyday tasks",
"high" => "Greater reasoning depth for complex problems",
@@ -67,7 +67,9 @@ impl ExecutionService for ModelsExecution {
)
.with_reasoning(
Some("medium".to_owned()),
["low", "medium", "high"].map(str::to_owned).to_vec(),
["low", "medium", "high", "xhigh"]
.map(str::to_owned)
.to_vec(),
)
.with_context_window_tokens(Some(500_000))
.with_image_input(true)
@@ -138,6 +140,27 @@ async fn models_should_encode_provider_profiles_for_current_codex_clients() {
assert_eq!(model["context_window"], 500_000);
assert_eq!(model["apply_patch_tool_type"], "freeform");
assert_eq!(model["service_tiers"], serde_json::json!([]));
assert_eq!(
model["supported_reasoning_levels"],
serde_json::json!([
{
"effort": "low",
"description": "Fast responses with lighter reasoning"
},
{
"effort": "medium",
"description": "Balances speed and reasoning depth for everyday tasks"
},
{
"effort": "high",
"description": "Greater reasoning depth for complex problems"
},
{
"effort": "xhigh",
"description": "Extra high reasoning depth for complex problems"
}
])
);
}
#[tokio::test]
+19 -18
View File
@@ -57,24 +57,25 @@ pub use transport::{
GrokBillingTransportError, GrokBillingTransportErrorKind, GrokBillingTransportFuture,
GrokBillingTransportResponse, GrokCanonicalDecoder, GrokCatalogApiBackend,
GrokCatalogCapabilities, GrokCatalogCapabilityEvidence, GrokCatalogLimits, GrokCatalogMetadata,
GrokCatalogModel, GrokClientIdentity, GrokCompactionDecodeError, GrokCompactionRequest,
GrokCompactionSummaryDecoder, GrokCredentialFailure, GrokCredentialFeedbackFuture,
GrokDnsResolutionError, GrokDnsResolutionPlan, GrokDnsResolutionPolicy, GrokEndpointPolicy,
GrokHeader, GrokHeaderValue, GrokInferenceChunkStream, GrokInferenceClientCacheStatus,
GrokInferenceDnsObservation, GrokInferenceDnsSource, GrokInferenceRequest,
GrokInferenceResponse, GrokInferenceTransport, GrokInferenceTransportError,
GrokInferenceTransportErrorKind, GrokInferenceTransportFuture, GrokInferenceTransportMetrics,
GrokModelCatalogClient, GrokModelCatalogError, GrokModelCatalogRequest,
GrokModelCatalogSession, GrokModelCatalogSessionError, GrokModelCatalogSnapshot,
GrokModelCatalogTransport, GrokModelCatalogTransportError, GrokModelCatalogTransportErrorKind,
GrokModelCatalogTransportFuture, GrokModelCatalogTransportResponse, GrokProviderConfigError,
GrokProviderTransport, GrokRequestEncodeError, GrokReqwestTransportBuildError,
GrokResponsesRequest, GrokSessionAffinityKey, GrokSessionBinding, GrokSessionDataError,
GrokSessionLeaseGuard, GrokSessionSelection, GrokSessionSelector, GrokSessionSelectorError,
GrokSessionSelectorFuture, MAX_GROK_BILLING_BYTES, MAX_GROK_MODEL_CATALOG_BYTES,
OfficialGrokEndpointPolicy, ReqwestGrokInferenceTransport, ReqwestGrokModelCatalogTransport,
ReqwestOAuthTransport, SelectedGrokSession, XAI_PROVIDER_NAME, build_grok_headers,
grok_billing_breakdown, parse_grok_billing, parse_grok_model_catalog,
GrokCatalogModel, GrokCatalogReasoningEffort, GrokClientIdentity, GrokCompactionDecodeError,
GrokCompactionRequest, GrokCompactionSummaryDecoder, GrokCredentialFailure,
GrokCredentialFeedbackFuture, GrokDnsResolutionError, GrokDnsResolutionPlan,
GrokDnsResolutionPolicy, GrokEndpointPolicy, GrokHeader, GrokHeaderValue,
GrokInferenceChunkStream, GrokInferenceClientCacheStatus, GrokInferenceDnsObservation,
GrokInferenceDnsSource, GrokInferenceRequest, GrokInferenceResponse, GrokInferenceTransport,
GrokInferenceTransportError, GrokInferenceTransportErrorKind, GrokInferenceTransportFuture,
GrokInferenceTransportMetrics, GrokModelCatalogClient, GrokModelCatalogError,
GrokModelCatalogRequest, GrokModelCatalogSession, GrokModelCatalogSessionError,
GrokModelCatalogSnapshot, GrokModelCatalogTransport, GrokModelCatalogTransportError,
GrokModelCatalogTransportErrorKind, GrokModelCatalogTransportFuture,
GrokModelCatalogTransportResponse, GrokProviderConfigError, GrokProviderTransport,
GrokRequestEncodeError, GrokReqwestTransportBuildError, GrokResponsesRequest,
GrokSessionAffinityKey, GrokSessionBinding, GrokSessionDataError, GrokSessionLeaseGuard,
GrokSessionSelection, GrokSessionSelector, GrokSessionSelectorError, GrokSessionSelectorFuture,
MAX_GROK_BILLING_BYTES, MAX_GROK_MODEL_CATALOG_BYTES, OfficialGrokEndpointPolicy,
ReqwestGrokInferenceTransport, ReqwestGrokModelCatalogTransport, ReqwestOAuthTransport,
SelectedGrokSession, XAI_PROVIDER_NAME, build_grok_headers, grok_billing_breakdown,
parse_grok_billing, parse_grok_model_catalog,
};
/// xAI 初始化后交给组装根的最小能力集。
+25 -5
View File
@@ -430,7 +430,9 @@ fn default_grok_model_presentation() -> ModelPresentation {
)
.with_reasoning(
Some("medium".to_owned()),
["low", "medium", "high"].map(str::to_owned).to_vec(),
["low", "medium", "high", "xhigh"]
.map(str::to_owned)
.to_vec(),
)
.with_context_window_tokens(Some(500_000))
.with_image_input(true)
@@ -444,17 +446,35 @@ fn grok_model_presentation(model: &GrokCatalogModel) -> ModelPresentation {
DEFAULT_GROK_MODEL | "grok-4.5-latest" | "grok-4.5-build-free" | "grok-build-latest"
);
let reasoning_evidence = model.capabilities().reasoning_effort();
let catalog_reasoning_efforts = model
.capabilities()
.reasoning_efforts()
.iter()
.map(|effort| effort.as_str().to_owned())
.collect::<Vec<_>>();
let catalog_default_reasoning = model
.capabilities()
.default_reasoning_effort()
.map(|effort| effort.as_str().to_owned());
let (default_reasoning, reasoning_efforts) = match reasoning_evidence {
GrokCatalogCapabilityEvidence::DeclaredNative if !catalog_reasoning_efforts.is_empty() => (
catalog_default_reasoning.or_else(|| catalog_reasoning_efforts.first().cloned()),
catalog_reasoning_efforts,
),
GrokCatalogCapabilityEvidence::DeclaredNative => (
Some("medium".to_owned()),
["low", "medium", "high"].map(str::to_owned).to_vec(),
catalog_default_reasoning.or_else(|| Some("medium".to_owned())),
["low", "medium", "high", "xhigh"]
.map(str::to_owned)
.to_vec(),
),
GrokCatalogCapabilityEvidence::DeclaredUnsupported => {
(Some("none".to_owned()), vec!["none".to_owned()])
}
GrokCatalogCapabilityEvidence::Unknown if known_grok_4_5 => (
Some("medium".to_owned()),
["low", "medium", "high"].map(str::to_owned).to_vec(),
catalog_default_reasoning.or_else(|| Some("medium".to_owned())),
["low", "medium", "high", "xhigh"]
.map(str::to_owned)
.to_vec(),
),
GrokCatalogCapabilityEvidence::Unknown => (None, Vec::new()),
};
@@ -589,11 +589,42 @@ pub enum GrokCatalogApiBackend {
Unknown,
}
/// Grok 官方目录允许声明的 reasoning effort。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum GrokCatalogReasoningEffort {
None,
Minimal,
Low,
Medium,
High,
Xhigh,
Max,
}
impl GrokCatalogReasoningEffort {
/// 返回 Responses API 使用的规范值。
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::None => "none",
Self::Minimal => "minimal",
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
Self::Xhigh => "xhigh",
Self::Max => "max",
}
}
}
/// Grok 目录中允许进入控制面的能力证据。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GrokCatalogCapabilities {
responses_api: GrokCatalogCapabilityEvidence,
reasoning_effort: GrokCatalogCapabilityEvidence,
reasoning_efforts: Vec<GrokCatalogReasoningEffort>,
default_reasoning_effort: Option<GrokCatalogReasoningEffort>,
backend_search: GrokCatalogCapabilityEvidence,
streaming_tool_calls: GrokCatalogCapabilityEvidence,
api_backend: Option<GrokCatalogApiBackend>,
@@ -612,6 +643,18 @@ impl GrokCatalogCapabilities {
self.reasoning_effort
}
/// 返回上游声明的可选 reasoning effort,顺序与目录一致。
#[must_use]
pub fn reasoning_efforts(&self) -> &[GrokCatalogReasoningEffort] {
&self.reasoning_efforts
}
/// 返回上游声明或菜单推导出的默认 reasoning effort。
#[must_use]
pub const fn default_reasoning_effort(&self) -> Option<GrokCatalogReasoningEffort> {
self.default_reasoning_effort
}
/// 返回后端 search 支持证据。
#[must_use]
pub const fn backend_search(&self) -> GrokCatalogCapabilityEvidence {
@@ -850,6 +893,10 @@ struct GrokModelWire {
alias = "supports_reasoning_effort"
)]
supports_reasoning_effort: Option<bool>,
#[serde(rename = "reasoningEffort", alias = "reasoning_effort")]
reasoning_effort: Option<GrokCatalogReasoningEffort>,
#[serde(default, rename = "reasoningEfforts", alias = "reasoning_efforts")]
reasoning_efforts: Vec<GrokReasoningEffortOptionWire>,
#[serde(rename = "supportsBackendSearch", alias = "supports_backend_search")]
supports_backend_search: Option<bool>,
#[serde(rename = "streamToolCalls", alias = "stream_tool_calls")]
@@ -857,6 +904,26 @@ struct GrokModelWire {
hidden: Option<bool>,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum GrokReasoningEffortOptionWire {
Bare(GrokCatalogReasoningEffort),
Detailed {
value: GrokCatalogReasoningEffort,
#[serde(default)]
default: bool,
},
}
impl GrokReasoningEffortOptionWire {
fn into_parts(self) -> (GrokCatalogReasoningEffort, bool) {
match self {
Self::Bare(value) => (value, false),
Self::Detailed { value, default } => (value, default),
}
}
}
/// 解析唯一受支持的 Grok 官方完整快照。
///
/// # Errors
@@ -922,15 +989,36 @@ fn normalize_model(wire: GrokModelWire) -> Result<GrokCatalogModel, GrokModelCat
.api_backend
.filter(|backend| *backend != GrokCatalogApiBackend::Unknown);
let responses_api = responses_evidence(wire.supported_in_api, api_backend);
let mut menu_default = None;
let reasoning_efforts = wire
.reasoning_efforts
.into_iter()
.map(|option| {
let (effort, is_default) = option.into_parts();
if is_default && menu_default.is_none() {
menu_default = Some(effort);
}
effort
})
.collect::<Vec<_>>();
let default_reasoning_effort = wire
.reasoning_effort
.or(menu_default)
.or_else(|| reasoning_efforts.first().copied());
let reasoning_effort = if reasoning_efforts.is_empty() {
GrokCatalogCapabilityEvidence::from_wire(wire.supports_reasoning_effort)
} else {
GrokCatalogCapabilityEvidence::DeclaredNative
};
Ok(GrokCatalogModel {
request_model,
display_name: wire.name,
capabilities: GrokCatalogCapabilities {
responses_api,
reasoning_effort: GrokCatalogCapabilityEvidence::from_wire(
wire.supports_reasoning_effort,
),
reasoning_effort,
reasoning_efforts,
default_reasoning_effort,
backend_search: GrokCatalogCapabilityEvidence::from_wire(wire.supports_backend_search),
streaming_tool_calls: GrokCatalogCapabilityEvidence::from_wire(wire.stream_tool_calls),
api_backend,
@@ -21,10 +21,10 @@ pub use catalog::{
GrokBillingRequest, GrokBillingSnapshot, GrokBillingTransport, GrokBillingTransportError,
GrokBillingTransportErrorKind, GrokBillingTransportFuture, GrokBillingTransportResponse,
GrokCatalogApiBackend, GrokCatalogCapabilities, GrokCatalogCapabilityEvidence,
GrokCatalogLimits, GrokCatalogMetadata, GrokCatalogModel, GrokModelCatalogClient,
GrokModelCatalogError, GrokModelCatalogRequest, GrokModelCatalogSession,
GrokModelCatalogSessionError, GrokModelCatalogSnapshot, GrokModelCatalogTransport,
GrokModelCatalogTransportError, GrokModelCatalogTransportErrorKind,
GrokCatalogLimits, GrokCatalogMetadata, GrokCatalogModel, GrokCatalogReasoningEffort,
GrokModelCatalogClient, GrokModelCatalogError, GrokModelCatalogRequest,
GrokModelCatalogSession, GrokModelCatalogSessionError, GrokModelCatalogSnapshot,
GrokModelCatalogTransport, GrokModelCatalogTransportError, GrokModelCatalogTransportErrorKind,
GrokModelCatalogTransportFuture, GrokModelCatalogTransportResponse, MAX_GROK_BILLING_BYTES,
MAX_GROK_MODEL_CATALOG_BYTES, parse_grok_billing, parse_grok_model_catalog,
};
@@ -1827,7 +1827,7 @@ async fn provider_compiles_realtime_catalog_capabilities() {
assert_eq!(presentation.display_name(), Some("Grok 4.5"));
assert_eq!(
presentation.supported_reasoning_efforts(),
["low", "medium", "high"]
["low", "medium", "high", "xhigh"]
);
assert_eq!(presentation.context_window_tokens(), Some(1_000_000));
assert!(presentation.agent_tools());
@@ -261,6 +261,16 @@ fn official_fixture_should_use_actual_model_and_whitelisted_metadata() {
(
model.capabilities().responses_api(),
model.capabilities().reasoning_effort(),
model
.capabilities()
.reasoning_efforts()
.iter()
.map(|effort| effort.as_str())
.collect::<Vec<_>>(),
model
.capabilities()
.default_reasoning_effort()
.map(|effort| effort.as_str()),
model.capabilities().backend_search(),
model.capabilities().streaming_tool_calls(),
),
@@ -278,6 +288,8 @@ fn official_fixture_should_use_actual_model_and_whitelisted_metadata() {
(
GrokCatalogCapabilityEvidence::DeclaredNative,
GrokCatalogCapabilityEvidence::DeclaredNative,
vec!["low", "medium", "high", "xhigh"],
Some("medium"),
GrokCatalogCapabilityEvidence::DeclaredNative,
GrokCatalogCapabilityEvidence::DeclaredNative,
),
@@ -335,6 +347,35 @@ fn missing_capability_fields_should_remain_unknown() {
);
}
#[test]
fn reasoning_effort_menu_should_be_capability_evidence_without_legacy_flag() {
let snapshot = parse_grok_model_catalog(
br#"{"object":"list","data":[{"id":"grok-reasoning","reasoningEfforts":[{"value":"xhigh","default":true},"low"]}]}"#,
None,
)
.expect("reasoning effort menu");
let capabilities = snapshot.models()[0].capabilities();
assert_eq!(
(
capabilities.reasoning_effort(),
capabilities
.reasoning_efforts()
.iter()
.map(|effort| effort.as_str())
.collect::<Vec<_>>(),
capabilities
.default_reasoning_effort()
.map(|effort| effort.as_str()),
),
(
GrokCatalogCapabilityEvidence::DeclaredNative,
vec!["xhigh", "low"],
Some("xhigh"),
)
);
}
#[test]
fn model_id_should_take_priority_over_catalog_id() {
let snapshot = parse_grok_model_catalog(
@@ -12,6 +12,13 @@
"apiBackend": "responses",
"supportedInApi": true,
"supportsReasoningEffort": true,
"reasoningEffort": "medium",
"reasoningEfforts": [
"low",
{ "value": "medium", "label": "Balanced", "default": true },
"high",
{ "id": "extended", "value": "xhigh", "label": "Extended" }
],
"supportsBackendSearch": true,
"streamToolCalls": true,
"hidden": false,
@@ -35,19 +35,30 @@ const configPath = computed(() =>
? '%userprofile%\\.codex\\config.toml'
: '~/.codex/config.toml',
)
const authPath = computed(() =>
activePlatform.value === 'windows' ? '%userprofile%\\.codex\\auth.json' : '~/.codex/auth.json',
const tokenPath = computed(() =>
activePlatform.value === 'windows'
? '%userprofile%\\.codex\\codex-proxy.token'
: '~/.codex/codex-proxy.token',
)
const codexAuthJson = computed(() => JSON.stringify({ OPENAI_API_KEY: keyValue.value }, null, 2))
const defaultModel = computed(() =>
props.apiKey?.providerKind?.trim().toLowerCase() === 'xai' ? 'grok-4.5' : 'gpt-5.5',
)
const providerAuthToml = computed(() =>
activePlatform.value === 'windows'
? `[model_providers.OpenAI.auth]
command = "powershell.exe"
args = ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", "Get-Content -Raw (Join-Path (Join-Path $env:USERPROFILE '.codex') 'codex-proxy.token')"]`
: `[model_providers.OpenAI.auth]
command = "sh"
args = ["-c", 'cat "$HOME/.codex/codex-proxy.token"']`,
)
const codexConfigToml = computed(
() => `model_provider = "OpenAI"
model = "${defaultModel.value}"
review_model = "${defaultModel.value}"
model_reasoning_effort = "xhigh"
service_tier = "default"
disable_response_storage = true
network_access = "enabled"
windows_wsl_setup_acknowledged = true
@@ -56,7 +67,8 @@ windows_wsl_setup_acknowledged = true
name = "OpenAI"
base_url = "${props.apiBaseUrl}"
wire_api = "responses"
requires_openai_auth = true
${providerAuthToml.value}
[features]
goals = true`,
@@ -64,7 +76,7 @@ goals = true`,
const visibleFiles = computed(() => [
{ path: configPath.value, content: codexConfigToml.value },
{ path: authPath.value, content: codexAuthJson.value },
{ path: tokenPath.value, content: keyValue.value },
])
</script>
+1 -1
View File
@@ -1 +1 @@
version: "3.0.7"
version: "3.0.8"