diff --git a/backend/crates/providers/openai/src/credential/admin.rs b/backend/crates/providers/openai/src/credential/admin.rs
index 7c76d1541..6469603ac 100644
--- a/backend/crates/providers/openai/src/credential/admin.rs
+++ b/backend/crates/providers/openai/src/credential/admin.rs
@@ -1288,9 +1288,9 @@ fn parse_oauth_import_tokens(
Value::Object(object) => {
for (key, value) in object {
let token = match key.as_str() {
- "accessToken" => &mut access_token,
- "refreshToken" => &mut refresh_token,
- "idToken" => &mut id_token,
+ "accessToken" | "access_token" => &mut access_token,
+ "refreshToken" | "refresh_token" => &mut refresh_token,
+ "idToken" | "id_token" => &mut id_token,
_ => {
pending.push(value);
continue;
diff --git a/backend/crates/providers/openai/tests/credential/admin.rs b/backend/crates/providers/openai/tests/credential/admin.rs
index 9c2657d06..4caffbace 100644
--- a/backend/crates/providers/openai/tests/credential/admin.rs
+++ b/backend/crates/providers/openai/tests/credential/admin.rs
@@ -59,6 +59,48 @@ async fn direct_import_persists_opaque_tokens_without_profile_or_token_validatio
assert!(account.account.next_refresh_at().is_none());
}
+#[tokio::test]
+async fn direct_import_accepts_snake_case_oauth_token_aliases() {
+ let service = CodexCredentialAdminService::new(
+ Arc::new(UnusedRefresher),
+ Arc::new(TestLeaseCoordinator::default()),
+ runtime_policy(),
+ );
+ let prepared = service
+ .prepare_import_document(serde_json::json!({
+ "accounts": [{
+ "platform": "openai",
+ "type": "oauth",
+ "credentials": {
+ "access_token": "snake-access-token",
+ "refresh_token": "snake-refresh-token",
+ "id_token": "snake-id-token"
+ }
+ }]
+ }))
+ .await
+ .expect("snake_case OAuth token aliases must be accepted");
+
+ let account = prepared.accounts().first().expect("one prepared account");
+ let runtime = CodexCredentialCodec::decode(&account.credential).expect("stored credential");
+ let oauth = runtime.authentication.oauth().expect("OAuth credential");
+ assert_eq!(
+ (
+ oauth.access_token.expose_secret(),
+ oauth
+ .refresh_token
+ .as_ref()
+ .map(|token| token.expose_secret()),
+ oauth.id_token.as_ref().map(|token| token.expose_secret()),
+ ),
+ (
+ "snake-access-token",
+ Some("snake-refresh-token"),
+ Some("snake-id-token"),
+ )
+ );
+}
+
#[tokio::test]
async fn direct_import_projects_access_token_jwt_expiry_without_persisting_refresh_margin() {
let expires_at = Utc
diff --git a/backend/crates/providers/openai/tests/transport/diagnostics.rs b/backend/crates/providers/openai/tests/transport/diagnostics.rs
index 6b2b44660..fac97b6fb 100644
--- a/backend/crates/providers/openai/tests/transport/diagnostics.rs
+++ b/backend/crates/providers/openai/tests/transport/diagnostics.rs
@@ -195,7 +195,7 @@ fn bare_http_429_remains_a_temporary_rate_limit() {
}
#[test]
-fn sub2api_openai_account_failure_matrix_is_preserved() {
+fn openai_account_failure_matrix_is_preserved() {
let cases = [
(
400,
diff --git a/backend/crates/providers/xai/src/credential/repository.rs b/backend/crates/providers/xai/src/credential/repository.rs
index a66e90a30..799385ea4 100644
--- a/backend/crates/providers/xai/src/credential/repository.rs
+++ b/backend/crates/providers/xai/src/credential/repository.rs
@@ -237,19 +237,19 @@ impl GrokCredentialAdmin {
let mut secret = decode_secret(&loaded.credential)?;
let mut credentials = serde_json::Map::new();
credentials.insert(
- "access_token".to_owned(),
+ "accessToken".to_owned(),
Value::String(std::mem::take(&mut secret.access_token)),
);
credentials.insert(
- "refresh_token".to_owned(),
+ "refreshToken".to_owned(),
Value::String(std::mem::take(&mut secret.refresh_token)),
);
if let Some(id_token) = secret.id_token.take() {
- credentials.insert("id_token".to_owned(), Value::String(id_token));
+ credentials.insert("idToken".to_owned(), Value::String(id_token));
}
- credentials.insert("token_type".to_owned(), Value::String("Bearer".to_owned()));
+ credentials.insert("tokenType".to_owned(), Value::String("Bearer".to_owned()));
credentials.insert(
- "expires_at".to_owned(),
+ "expiresAt".to_owned(),
Value::String(
loaded
.account
@@ -260,11 +260,11 @@ impl GrokCredentialAdmin {
),
);
credentials.insert(
- "base_url".to_owned(),
+ "baseUrl".to_owned(),
Value::String(GROK_CLI_BASE_URL.to_owned()),
);
credentials.insert(
- "client_id".to_owned(),
+ "clientId".to_owned(),
Value::String(OFFICIAL_CLIENT_ID.to_owned()),
);
credentials.insert(
@@ -290,7 +290,7 @@ impl GrokCredentialAdmin {
Ok(GrokAccountExport(serde_json::json!({
"version": 1,
"type": "oauth-account-bundle",
- "exported_at": exported_at.to_rfc3339(),
+ "exportedAt": exported_at.to_rfc3339(),
"accounts": exported_accounts,
"proxies": [],
})))
diff --git a/backend/crates/providers/xai/src/provider.rs b/backend/crates/providers/xai/src/provider.rs
index 67403ed49..a186caeca 100644
--- a/backend/crates/providers/xai/src/provider.rs
+++ b/backend/crates/providers/xai/src/provider.rs
@@ -2076,7 +2076,7 @@ const WORKER_LEASE_RENEWAL: Duration = Duration::from_secs(5 * 60);
const OAUTH_REFRESH_INTERVAL: Duration = Duration::from_secs(30);
const QUOTA_CATALOG_INTERVAL: Duration = Duration::from_secs(5 * 60);
// rolling 24h 描述的是上游用量窗口,不代表从本次观测起封禁 24 小时;
-// 缺少可信 reset 时间时按 sub2api 的短周期探测策略恢复检查。
+// 缺少可信 reset 时间时按短周期探测策略恢复检查。
const EXHAUSTED_QUOTA_FALLBACK_RECHECK_INTERVAL: Duration = Duration::from_secs(10 * 60);
const EXHAUSTED_QUOTA_REFRESH_RETRY_INTERVAL: Duration = QUOTA_CATALOG_INTERVAL;
const CLI_RELEASE_WORKER_OWNER: &str = "xai-cli-release";
diff --git a/backend/crates/providers/xai/src/transport/compaction.rs b/backend/crates/providers/xai/src/transport/compaction.rs
index 80b251b52..d9b841ae7 100644
--- a/backend/crates/providers/xai/src/transport/compaction.rs
+++ b/backend/crates/providers/xai/src/transport/compaction.rs
@@ -108,7 +108,7 @@ impl GrokCompactionRequest {
self.reasoning_replay_session_id.as_deref()
}
- /// 返回经 sub2api 别名表归一化后的 xAI wire 模型。
+ /// 返回归一化后的 xAI wire 模型。
pub(crate) fn upstream_model(&self) -> Option<&str> {
self.body.get("model").and_then(Value::as_str)
}
@@ -286,8 +286,7 @@ pub(crate) fn compaction_wire_events(
Value::Null,
);
let item = compaction_item(summary, encrypted_content);
- // sub2api 的 compact contract 以真实密文为成功依据,不向客户端延续
- // 摘要生成阶段的 incomplete 状态。
+ // compact 成功以真实密文为依据,不向客户端延续摘要生成阶段的 incomplete 状态。
let terminal_response = compaction_response(
completed,
terminal_source.or(created_source),
diff --git a/backend/crates/providers/xai/src/transport/request.rs b/backend/crates/providers/xai/src/transport/request.rs
index 889c6ff1b..64e86ecd5 100644
--- a/backend/crates/providers/xai/src/transport/request.rs
+++ b/backend/crates/providers/xai/src/transport/request.rs
@@ -91,7 +91,7 @@ impl GrokResponsesRequest {
self.reasoning_replay_session_id.as_deref()
}
- /// 返回经 sub2api 别名表归一化后的 xAI wire 模型。
+ /// 返回归一化后的 xAI wire 模型。
pub(crate) fn upstream_model(&self) -> Option<&str> {
self.body.get("model").and_then(Value::as_str)
}
@@ -1928,8 +1928,8 @@ impl ToolNormalizer {
"apply_patch" => self.normalize_apply_patch_tool(tool),
"x_search" | "collections_search" | "file_search" | "code_execution"
| "code_interpreter" => Ok(vec![Value::Object(without_defer_loading(tool))]),
- // sub2api 的 xAI Responses 适配层对未获 xAI 接受的工具做静默过滤,
- // 并在过滤后同步收敛 tool_choice;不要把客户端可选扩展升级成整单 400。
+ // 对未获 xAI 接受的工具做静默过滤,并在过滤后同步收敛 tool_choice;
+ // 不要把客户端可选扩展升级成整单 400。
"" | "computer_use_preview" | "image_generation" => Ok(Vec::new()),
_ => Ok(Vec::new()),
}
@@ -3517,15 +3517,15 @@ fn normalize_compaction_input(
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty());
- let has_sub2api_shape = item.contains_key("summary")
+ let has_structured_compaction_shape = item.contains_key("summary")
|| item.contains_key("id")
|| item.contains_key("status")
|| string_field(item, "type").trim() == "compaction_summary";
// 旧版 adapter 曾将明文摘要直接写入 encrypted_content,且不带
- // id/status/summary。仅对这一可识别的历史形态保留明文恢复;新形态严格按
- // sub2api 回放为 xAI reasoning 密文 + 可见 conversation summary。
- if !has_sub2api_shape {
+ // id/status/summary。仅对这一可识别的历史形态保留明文恢复;新形态严格回放为
+ // xAI reasoning 密文 + 可见 conversation summary。
+ if !has_structured_compaction_shape {
let Some(summary) = encrypted else {
return Ok(Vec::new());
};
diff --git a/backend/crates/providers/xai/tests/credential/contract.rs b/backend/crates/providers/xai/tests/credential/contract.rs
index 515f571f9..4aa8ee1f3 100644
--- a/backend/crates/providers/xai/tests/credential/contract.rs
+++ b/backend/crates/providers/xai/tests/credential/contract.rs
@@ -363,20 +363,48 @@ async fn oauth_bundle_export_is_provider_owned_canonical_and_debug_redacted() {
let value = export.into_value();
assert_eq!(value["version"], 1);
assert_eq!(value["type"], "oauth-account-bundle");
+ assert!(value.get("exportedAt").is_some());
+ assert!(value.get("exported_at").is_none());
assert_eq!(value["accounts"][0]["platform"], "grok");
assert_eq!(value["accounts"][0]["type"], "oauth");
assert_eq!(
- value["accounts"][0]["credentials"]["base_url"],
+ value["accounts"][0]["credentials"]["baseUrl"],
provider_xai::GROK_CLI_BASE_URL
);
assert_eq!(
- value["accounts"][0]["credentials"]["access_token"],
+ value["accounts"][0]["credentials"]["accessToken"],
"access-export"
);
+ assert_eq!(
+ value["accounts"][0]["credentials"]["refreshToken"],
+ "refresh-export"
+ );
+ assert_eq!(value["accounts"][0]["credentials"]["idToken"], "id-export");
+ assert_eq!(value["accounts"][0]["credentials"]["tokenType"], "Bearer");
+ assert!(
+ value["accounts"][0]["credentials"]["expiresAt"]
+ .as_str()
+ .is_some()
+ );
+ assert_eq!(
+ value["accounts"][0]["credentials"]["clientId"],
+ provider_xai::OFFICIAL_CLIENT_ID
+ );
assert_eq!(
value["accounts"][0]["credentials"]["scope"],
"openid profile email offline_access grok-cli:access api:access"
);
+ for field in [
+ "access_token",
+ "refresh_token",
+ "id_token",
+ "token_type",
+ "expires_at",
+ "base_url",
+ "client_id",
+ ] {
+ assert!(value["accounts"][0]["credentials"].get(field).is_none());
+ }
assert_eq!(value["proxies"], serde_json::json!([]));
}
diff --git a/backend/crates/providers/xai/tests/credential/import.rs b/backend/crates/providers/xai/tests/credential/import.rs
index b192ecda7..0f32ad5b3 100644
--- a/backend/crates/providers/xai/tests/credential/import.rs
+++ b/backend/crates/providers/xai/tests/credential/import.rs
@@ -225,7 +225,7 @@ fn oauth_account_document_should_reject_api_key_credential_field() {
fn oauth_account_document_should_ignore_source_wrapper_and_non_auth_metadata() {
let mut document: serde_json::Value =
serde_json::from_slice(&oauth_account_document()).expect("fixture JSON");
- document["type"] = serde_json::Value::String("sub2api-data".to_owned());
+ document["type"] = serde_json::Value::String("external-oauth-data".to_owned());
document["version"] = serde_json::Value::from(99);
document["proxies"] = serde_json::json!([{"url": "http://127.0.0.1:8080"}]);
document["source_extension"] = serde_json::json!({"opaque": true});
diff --git a/backend/crates/providers/xai/tests/provider/contract.rs b/backend/crates/providers/xai/tests/provider/contract.rs
index 5624e4a78..9136e446a 100644
--- a/backend/crates/providers/xai/tests/provider/contract.rs
+++ b/backend/crates/providers/xai/tests/provider/contract.rs
@@ -1534,7 +1534,7 @@ async fn compaction_wire_should_exclude_reasoning_from_summary_content() {
}
#[tokio::test]
-async fn compaction_stream_should_accept_short_summary_like_sub2api() {
+async fn compaction_stream_should_accept_short_summary() {
let transport = StubInferenceTransport::sequence([InferenceMode::SuccessBody(compaction_sse(
"too short",
None,
diff --git a/backend/crates/providers/xai/tests/transport/compaction.rs b/backend/crates/providers/xai/tests/transport/compaction.rs
index 994b04c71..7dea55242 100644
--- a/backend/crates/providers/xai/tests/transport/compaction.rs
+++ b/backend/crates/providers/xai/tests/transport/compaction.rs
@@ -76,7 +76,7 @@ fn encoder_should_preserve_history_order_and_append_summary_prompt() {
}
#[test]
-fn encoder_should_preserve_sub2api_compaction_constraints() {
+fn encoder_should_preserve_compaction_constraints() {
let request = encode(json!({
"model": "client-model",
"input": [
@@ -190,7 +190,7 @@ fn encoder_should_consume_only_the_terminal_compaction_trigger() {
}
#[test]
-fn decoder_should_preserve_sub2api_summary_text_after_trimming_edges() {
+fn decoder_should_preserve_summary_text_after_trimming_edges() {
let raw = valid_summary("checkpoint-only-secret");
let summary = decode_summary(&format!(" {raw} \n")).expect("summary");
@@ -234,7 +234,7 @@ fn decoder_should_preserve_unclosed_leading_analysis() {
}
#[test]
-fn decoder_should_preserve_compaction_control_tokens_like_sub2api() {
+fn decoder_should_preserve_compaction_control_tokens() {
let raw = valid_summary(
"Quoted and , and , \
plus and .",
@@ -278,7 +278,7 @@ fn decoder_should_preserve_numbered_sections_that_quote_analysis_close() {
}
#[test]
-fn decoder_should_accept_short_summary_like_sub2api() {
+fn decoder_should_accept_short_summary() {
let summary = decode_summary("too short").expect("short summary");
assert_eq!(summary, "too short");
diff --git a/backend/crates/providers/xai/tests/transport/network.rs b/backend/crates/providers/xai/tests/transport/network.rs
index 53ff961fe..f843a1bcd 100644
--- a/backend/crates/providers/xai/tests/transport/network.rs
+++ b/backend/crates/providers/xai/tests/transport/network.rs
@@ -567,7 +567,7 @@ async fn inference_transport_should_classify_free_usage_429_as_account_free_quot
}
#[tokio::test]
-async fn inference_transport_should_apply_sub2api_body_aware_400_failures() {
+async fn inference_transport_should_apply_body_aware_400_failures() {
let cases = [
(
json!({"error": {"code": "subscription:free-usage-exhausted", "message": "free usage exhausted"}}),
diff --git a/backend/crates/providers/xai/tests/transport/request.rs b/backend/crates/providers/xai/tests/transport/request.rs
index c0d9e21c4..2784bbde5 100644
--- a/backend/crates/providers/xai/tests/transport/request.rs
+++ b/backend/crates/providers/xai/tests/transport/request.rs
@@ -198,7 +198,7 @@ fn encoder_should_apply_build_defaults_and_normalize_reasoning_effort() {
}
#[test]
-fn encoder_should_strip_sub2api_grok_unsupported_fields() {
+fn encoder_should_strip_grok_unsupported_fields() {
let request = raw_request(json!({
"model": "client-model",
"input": "hello",
@@ -943,7 +943,7 @@ fn custom_apply_patch_declaration_should_use_patch_parameter() {
}
#[test]
-fn hosted_tool_choice_should_preserve_the_sub2api_specific_choice() {
+fn hosted_tool_choice_should_preserve_normalized_web_search_choice() {
let request = raw_request(json!({
"model": "client",
"input": "search",
@@ -1244,7 +1244,7 @@ fn compaction_history_should_become_plaintext_user_continuation_in_place() {
}
#[test]
-fn sub2api_compaction_history_should_restore_reasoning_ciphertext_and_visible_summary() {
+fn structured_compaction_history_should_restore_reasoning_ciphertext_and_visible_summary() {
for item_type in ["compaction", "compaction_summary"] {
let request = raw_request(json!({
"model": "client",
@@ -1264,7 +1264,7 @@ fn sub2api_compaction_history_should_restore_reasoning_ciphertext_and_visible_su
}));
let encoded = GrokResponsesRequest::encode(&request, "grok-4.5", &client_key())
- .expect("sub2api compaction continuation");
+ .expect("structured compaction continuation");
let body = Value::Object(encoded.body().clone());
assert_eq!(
@@ -1290,7 +1290,7 @@ fn sub2api_compaction_history_should_restore_reasoning_ciphertext_and_visible_su
}
#[test]
-fn malformed_compaction_history_should_be_dropped_like_sub2api() {
+fn malformed_compaction_history_should_be_dropped() {
for encrypted_content in [
None,
Some(json!(null)),