///|
priv enum MoonBalanceProvider {
DeepSeek
StepFun
SiliconFlow
SiliconFlowEn
OpenRouter
NovitaAI
}
///|
fn moon_detect_balance_provider(base_url : String) -> MoonBalanceProvider? {
let url = base_url.to_lower()
if url.contains("api.deepseek.com") {
Some(DeepSeek)
} else if url.contains("api.stepfun.ai") || url.contains("api.stepfun.com") {
Some(StepFun)
} else if url.contains("api.siliconflow.cn") {
Some(SiliconFlow)
} else if url.contains("api.siliconflow.com") {
Some(SiliconFlowEn)
} else if url.contains("openrouter.ai") {
Some(OpenRouter)
} else if url.contains("api.novita.ai") {
Some(NovitaAI)
} else {
None
}
}
///|
fn moon_balance_error(message : String) -> UsageResult {
UsageResult::new(false, error=Some(message))
}
///|
fn moon_balance_auth_error(status_code : Int) -> UsageResult {
UsageResult::new(
false,
data=Some([
UsageData::new(
is_valid=Some(false),
invalid_message=Some("Authentication failed (HTTP \{status_code})"),
),
]),
error=Some("Authentication failed (HTTP \{status_code})"),
)
}
///|
async fn moon_get_balance(base_url : String, api_key : String) -> UsageResult {
if api_key.trim().is_empty() {
return moon_balance_error("API key is empty")
}
let provider = match moon_detect_balance_provider(base_url) {
Some(provider) => provider
None => return moon_balance_error("Unknown balance provider")
}
match provider {
DeepSeek => moon_query_deepseek_balance(api_key)
StepFun => moon_query_stepfun_balance(api_key)
SiliconFlow => moon_query_siliconflow_balance(api_key, true)
SiliconFlowEn => moon_query_siliconflow_balance(api_key, false)
OpenRouter => moon_query_openrouter_balance(api_key)
NovitaAI => moon_query_novita_balance(api_key)
}
}
///|
async fn moon_balance_fetch_json(
url : String,
api_key : String,
) -> Result[Json, UsageResult] {
let target = match models_url_target(url) {
Ok(target) => target
Err(err) => return Err(moon_balance_error(err))
}
let client = @http.Client::Client(target.origin, headers={
"Authorization": "Bearer \{api_key}",
"Accept": "application/json",
})
defer client.close()
let response = client.get(target.path) catch {
err => return Err(moon_balance_error("Network error: \{err}"))
}
let body = client.read_all().text()
if response.code == 401 || response.code == 403 {
return Err(moon_balance_auth_error(response.code))
}
if !(response.code is (200..=299)) {
return Err(moon_balance_error("API error (HTTP \{response.code}): \{body}"))
}
try @json.parse(body) catch {
err => Err(moon_balance_error("Failed to parse response: \{err}"))
} noraise {
json => Ok(json)
}
}
///|
async fn moon_query_deepseek_balance(api_key : String) -> UsageResult {
match
moon_balance_fetch_json("https://api.deepseek.com/user/balance", api_key) {
Err(result) => result
Ok(body) => moon_parse_deepseek_balance(body)
}
}
///|
fn moon_parse_deepseek_balance(body : Json) -> UsageResult {
let mut is_available = true
let data : Array[UsageData] = []
guard body is Object(obj) else {
return moon_balance_error("Failed to parse response: expected object")
}
match obj.get("is_available") {
Some(True) => is_available = true
Some(False) => is_available = false
_ => ()
}
match obj.get("balance_infos") {
Some(Array(infos)) =>
for info in infos {
guard info is Object(info_obj) else { continue }
let currency = json_string_from_map(info_obj, "currency").unwrap_or(
"CNY",
)
data.push(
UsageData::new(
plan_name=Some(currency),
remaining=moon_balance_number_field(info_obj, "total_balance"),
unit=Some(currency),
is_valid=Some(is_available),
invalid_message=if is_available {
None
} else {
Some("Insufficient balance")
},
),
)
}
_ => ()
}
UsageResult::new(true, data=if data.is_empty() { None } else { Some(data) })
}
///|
async fn moon_query_stepfun_balance(api_key : String) -> UsageResult {
match
moon_balance_fetch_json("https://api.stepfun.com/v1/accounts", api_key) {
Err(result) => result
Ok(body) => moon_parse_stepfun_balance(body)
}
}
///|
fn moon_parse_stepfun_balance(body : Json) -> UsageResult {
guard body is Object(obj) else {
return moon_balance_error("Failed to parse response: expected object")
}
let balance = moon_balance_number_field(obj, "balance").unwrap_or(0.0)
UsageResult::new(
true,
data=Some([
UsageData::new(
plan_name=Some("StepFun"),
remaining=Some(balance),
unit=Some("CNY"),
is_valid=Some(true),
),
]),
)
}
///|
async fn moon_query_siliconflow_balance(
api_key : String,
is_cn : Bool,
) -> UsageResult {
let domain = if is_cn { "api.siliconflow.cn" } else { "api.siliconflow.com" }
match moon_balance_fetch_json("https://\{domain}/v1/user/info", api_key) {
Err(result) => result
Ok(body) => moon_parse_siliconflow_balance(body, is_cn)
}
}
///|
fn moon_parse_siliconflow_balance(body : Json, is_cn : Bool) -> UsageResult {
guard body is Object(obj) else {
return moon_balance_error("Failed to parse response: expected object")
}
guard obj.get("data") is Some(Object(data)) else {
return moon_balance_error("Missing 'data' field in response")
}
let total_balance = moon_balance_number_field(data, "totalBalance").unwrap_or(
0.0,
)
UsageResult::new(
true,
data=Some([
UsageData::new(
plan_name=Some(if is_cn { "SiliconFlow" } else { "SiliconFlow (EN)" }),
remaining=Some(total_balance),
unit=Some(if is_cn { "CNY" } else { "USD" }),
is_valid=Some(true),
),
]),
)
}
///|
async fn moon_query_openrouter_balance(api_key : String) -> UsageResult {
match
moon_balance_fetch_json("https://openrouter.ai/api/v1/credits", api_key) {
Err(result) => result
Ok(body) => moon_parse_openrouter_balance(body)
}
}
///|
fn moon_parse_openrouter_balance(body : Json) -> UsageResult {
guard body is Object(root) else {
return moon_balance_error("Failed to parse response: expected object")
}
let data = match root.get("data") {
Some(Object(data)) => data
_ => root
}
let total = moon_balance_number_field(data, "total_credits").unwrap_or(0.0)
let used = moon_balance_number_field(data, "total_usage").unwrap_or(0.0)
let remaining = total - used
UsageResult::new(
true,
data=Some([
UsageData::new(
plan_name=Some("OpenRouter"),
remaining=Some(remaining),
total=Some(total),
used=Some(used),
unit=Some("USD"),
is_valid=Some(remaining > 0.0),
invalid_message=if remaining <= 0.0 {
Some("No credits remaining")
} else {
None
},
),
]),
)
}
///|
async fn moon_query_novita_balance(api_key : String) -> UsageResult {
match
moon_balance_fetch_json("https://api.novita.ai/v3/user/balance", api_key) {
Err(result) => result
Ok(body) => moon_parse_novita_balance(body)
}
}
///|
fn moon_parse_novita_balance(body : Json) -> UsageResult {
guard body is Object(obj) else {
return moon_balance_error("Failed to parse response: expected object")
}
let available = moon_balance_number_field(obj, "availableBalance").unwrap_or(
0.0,
) /
10000.0
UsageResult::new(
true,
data=Some([
UsageData::new(
plan_name=Some("Novita AI"),
remaining=Some(available),
unit=Some("USD"),
is_valid=Some(available > 0.0),
invalid_message=if available <= 0.0 {
Some("No balance remaining")
} else {
None
},
),
]),
)
}
///|
fn moon_balance_number_field(obj : Map[String, Json], key : String) -> Double? {
match obj.get(key) {
Some(Number(value, ..)) => Some(value)
Some(String(value)) => parse_non_negative_decimal_to_double(value)
_ => None
}
}