agent-Specialization/modules/balance_client.py

192 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Admin balance fetchers for Kimi (Moonshot), DeepSeek, and Qwen (Aliyun BSS).
Credentials (优先读取仓库根目录 .env其次环境变量):
- MOONSHOT_API_KEY : Bearer token for Kimi
- DEEPSEEK_API_KEY : Bearer token for DeepSeek
- ALIYUN_ACCESS_KEY_ID : AccessKey ID for Aliyun (Qwen billing)
- ALIYUN_ACCESS_KEY_SECRET : AccessKey Secret for Aliyun (Qwen billing)
- USD_CNY_RATE : Override USD->CNY rate (default 7.1)
"""
from __future__ import annotations
import base64
import hashlib
import hmac
import json
import os
import time
import uuid
from pathlib import Path
from typing import Any, Dict, Tuple
from urllib import parse, request
# -------- .env 读取助手 --------
_DOTENV_CACHE: Dict[str, str] | None = None
def _read_dotenv() -> Dict[str, str]:
global _DOTENV_CACHE
if _DOTENV_CACHE is not None:
return _DOTENV_CACHE
path = Path(__file__).resolve().parents[1] / ".env"
result: Dict[str, str] = {}
if path.exists():
try:
for raw_line in path.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
key = key.strip()
value = value.strip().strip('"').strip("'")
if key:
result[key] = value
except Exception:
pass
_DOTENV_CACHE = result
return result
def _env(key: str, default: str | None = None) -> str | None:
if key in os.environ:
return os.environ[key]
return _read_dotenv().get(key, default)
USD_CNY_RATE = float(_env("USD_CNY_RATE", "7.1") or "7.1")
def _http_get(url: str, headers: Dict[str, str] | None = None, timeout: int = 8) -> Tuple[Dict[str, Any] | None, str | None]:
"""Perform a simple GET request, return (json, error_message)."""
req = request.Request(url, headers=headers or {})
try:
with request.urlopen(req, timeout=timeout) as resp:
data = resp.read()
return json.loads(data.decode("utf-8")), None
except Exception as exc: # broad: network/parsing errors
return None, str(exc)
# -------- Kimi (Moonshot) --------
def fetch_kimi_balance() -> Dict[str, Any]:
api_key = _env("MOONSHOT_API_KEY") or _env("API_KEY_KIMI") or _env("AGENT_API_KEY")
if not api_key:
return {"success": False, "error": "Kimi 密钥未设置MOONSHOT_API_KEY / API_KEY_KIMI / AGENT_API_KEY"}
url = "https://api.moonshot.ai/v1/users/me/balance"
payload, err = _http_get(url, headers={"Authorization": f"Bearer {api_key}"})
if err:
return {"success": False, "error": err}
try:
data = payload.get("data") or {}
available = float(data.get("available_balance", 0))
voucher = float(data.get("voucher_balance", 0))
cash = float(data.get("cash_balance", 0))
return {
"success": True,
"currency": "USD",
"available": available,
"available_cny": round(available * USD_CNY_RATE, 2),
"voucher": voucher,
"cash": cash,
"rate": USD_CNY_RATE,
"raw": payload,
}
except Exception as exc: # pragma: no cover
return {"success": False, "error": f"解析失败: {exc}", "raw": payload}
# -------- DeepSeek --------
def fetch_deepseek_balance() -> Dict[str, Any]:
api_key = _env("DEEPSEEK_API_KEY") or _env("API_KEY_DEEPSEEK")
if not api_key:
return {"success": False, "error": "DeepSeek 密钥未设置DEEPSEEK_API_KEY / API_KEY_DEEPSEEK"}
url = "https://api.deepseek.com/user/balance"
payload, err = _http_get(url, headers={"Authorization": f"Bearer {api_key}"})
if err:
return {"success": False, "error": err}
try:
infos = payload.get("balance_infos") or []
primary = infos[0] if infos else {}
total = float(primary.get("total_balance") or 0)
granted = float(primary.get("granted_balance") or 0)
topped_up = float(primary.get("topped_up_balance") or 0)
return {
"success": True,
"currency": primary.get("currency", "CNY"),
"available": total,
"granted": granted,
"topped_up": topped_up,
"raw": payload,
}
except Exception as exc: # pragma: no cover
return {"success": False, "error": f"解析失败: {exc}", "raw": payload}
# -------- Qwen (Aliyun BSS QueryAccountBalance) --------
def _percent_encode(val: str) -> str:
res = parse.quote(str(val), safe="~")
return res.replace("+", "%20").replace("*", "%2A").replace("%7E", "~")
def _sign(params: Dict[str, Any], secret: str) -> str:
sorted_params = sorted(params.items(), key=lambda x: x[0])
canonicalized = "&".join(f"{_percent_encode(k)}={_percent_encode(v)}" for k, v in sorted_params)
string_to_sign = f"GET&%2F&{_percent_encode(canonicalized)}"
h = hmac.new((secret + "&").encode("utf-8"), string_to_sign.encode("utf-8"), hashlib.sha1)
return base64.b64encode(h.digest()).decode("utf-8")
def fetch_qwen_balance() -> Dict[str, Any]:
ak = _env("ALIYUN_ACCESS_KEY_ID")
sk = _env("ALIYUN_ACCESS_KEY_SECRET")
if not ak or not sk:
return {"success": False, "error": "缺少 ALIYUN_ACCESS_KEY_ID / ALIYUN_ACCESS_KEY_SECRET"}
params: Dict[str, Any] = {
"Format": "JSON",
"Version": "2017-12-14",
"AccessKeyId": ak,
"SignatureMethod": "HMAC-SHA1",
"Timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"SignatureVersion": "1.0",
"SignatureNonce": str(uuid.uuid4()),
"Action": "QueryAccountBalance",
"RegionId": "cn-hangzhou",
}
signature = _sign(params, sk)
params["Signature"] = signature
url = "https://bss.aliyuncs.com/?" + parse.urlencode(params)
payload, err = _http_get(url)
if err:
return {"success": False, "error": err}
try:
data = payload.get("Data") or {}
amount = float(data.get("AvailableAmount") or 0)
currency = data.get("Currency", "CNY")
return {
"success": True,
"currency": currency,
"available": amount,
"cash": float(data.get("AvailableCashAmount") or 0),
"raw": payload,
}
except Exception as exc: # pragma: no cover
return {"success": False, "error": f"解析失败: {exc}", "raw": payload}
def fetch_all_balances() -> Dict[str, Any]:
return {
"rate_usd_cny": USD_CNY_RATE,
"kimi": fetch_kimi_balance(),
"deepseek": fetch_deepseek_balance(),
"qwen": fetch_qwen_balance(),
}