Skip to content
Merged
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
41 changes: 41 additions & 0 deletions rust/src/providers/minimax/coding_plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,15 @@ pub(super) fn parse_coding_plan_value(
parse_remains(json, now)
}

/// True when the coding-plan endpoint reports that this account has no coding
/// plan because it runs on a Token Plan subscription instead — live evidence:
/// base_resp 2062 "no active token plan subscription" (issue #254). Such
/// accounts are served by the console token-plan endpoints, so `mod.rs` uses
/// this to switch fetch paths.
pub(crate) fn is_token_plan_without_coding_plan(err: &ProviderError) -> bool {
matches!(err, ProviderError::Other(msg) if msg.to_ascii_lowercase().contains("token plan"))
}

#[cfg(test)]
#[cfg(test)]
mod tests {
Expand Down Expand Up @@ -895,4 +904,36 @@ mod tests {
_ => panic!("expected Services"),
}
}

#[test]
fn token_plan_predicate_matches_reporter_2062_message() {
// Live reporter evidence (issue #254): the legacy remains endpoint 200s
// with base_resp 2062 for Token Plan accounts.
let json = serde_json::json!({
"base_resp": {
"status_code": 2062,
"status_msg": "no active token plan subscription"
}
});
let err = parse_coding_plan_value(&json, now()).unwrap_err();
assert!(matches!(err, ProviderError::Other(_)));
assert!(is_token_plan_without_coding_plan(&err));
}

#[test]
fn token_plan_predicate_rejects_unrelated_errors() {
assert!(!is_token_plan_without_coding_plan(
&ProviderError::AuthRequired
));
assert!(!is_token_plan_without_coding_plan(&ProviderError::Other(
"MiniMax coding plan status 2000".to_string()
)));
assert!(!is_token_plan_without_coding_plan(&ProviderError::Parse(
"Missing coding plan data.".to_string()
)));
// Case-insensitive match on the message itself.
assert!(is_token_plan_without_coding_plan(&ProviderError::Other(
"No Active Token Plan Subscription".to_string()
)));
}
}
28 changes: 26 additions & 2 deletions rust/src/providers/minimax/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
mod coding_plan;
mod coding_plan_html;
mod local_storage;
mod token_plan;

// Re-exports for local storage import
#[allow(unused_imports)]
Expand Down Expand Up @@ -612,9 +613,32 @@ impl MiniMaxProvider {
cookie_header: &str,
region: MiniMaxRegion,
) -> Result<ProviderFetchResult, ProviderError> {
let snapshot = self
let snapshot = match self
.fetch_coding_plan_with_cookie(cookie_header, region)
.await?;
.await
{
Ok(snapshot) => snapshot,
Err(err) if coding_plan::is_token_plan_without_coding_plan(&err) => {
// Token Plan accounts have no coding-plan subscription: the
// legacy remains endpoint answers base_resp 2062 ("no active
// token plan subscription"). Fall back to the console
// token-plan endpoints (issue #254); when they carry nothing,
// surface the original coding-plan error unchanged.
match token_plan::fetch_token_plan_with_cookie(self, cookie_header, region).await {
Ok(result) => return Ok(result),
Err(token_plan_err @ ProviderError::AuthRequired) => {
return Err(token_plan_err);
}
Err(token_plan_err) => {
tracing::debug!(
"MiniMax token-plan fallback unavailable: {token_plan_err}"
);
return Err(err);
}
}
}
Err(err) => return Err(err),
};
let mut result = ProviderFetchResult::new(snapshot, "web");

// Best-effort billing enrichment — never kills the fetch.
Expand Down
Loading
Loading