sub2api_adapter.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. """Adapter for using Sub2API via the existing CPA-style interface."""
  2. from __future__ import annotations
  3. from .sub2api_client import Sub2ApiClient
  4. class Sub2ApiAdapter:
  5. """把 Sub2ApiClient 适配成 CpaClient 兼容接口, 让 ops/ 代码透明切换."""
  6. def __init__(self, client: Sub2ApiClient):
  7. self.client = client
  8. self._name_to_id: dict[str, int] = {}
  9. def _cache_account(self, account: dict) -> None:
  10. name = str(account.get("name") or "").strip()
  11. account_id = account.get("id")
  12. if name and isinstance(account_id, int):
  13. self._name_to_id[name] = account_id
  14. def _content_from_account(self, account: dict | None) -> dict | None:
  15. if not isinstance(account, dict):
  16. return None
  17. credentials = account.get("credentials")
  18. if isinstance(credentials, dict):
  19. return credentials
  20. return None
  21. def health_check(self) -> bool:
  22. return self.client.health_check()
  23. def _iter_accounts(self) -> list[dict]:
  24. accounts: list[dict] = []
  25. page = 1
  26. while True:
  27. payload = self.client.list_accounts(platform="openai", page=page, page_size=100)
  28. items = payload.get("items") if isinstance(payload, dict) else []
  29. if not isinstance(items, list) or not items:
  30. break
  31. for item in items:
  32. if isinstance(item, dict):
  33. accounts.append(item)
  34. pages = int(payload.get("pages") or page) if isinstance(payload, dict) else page
  35. if page >= pages:
  36. break
  37. page += 1
  38. return accounts
  39. def list_auth_files(self) -> list[dict]:
  40. result: list[dict] = []
  41. for item in self._iter_accounts():
  42. self._cache_account(item)
  43. content = self._content_from_account(item)
  44. if content is None:
  45. continue
  46. name = str(item.get("name") or "").strip()
  47. if not name:
  48. continue
  49. result.append({"name": name, "content": content})
  50. return result
  51. def upload_auth_file(self, name: str, content: dict) -> bool:
  52. credentials = {
  53. key: value
  54. for key, value in {
  55. "refresh_token": content.get("refresh_token"),
  56. "access_token": content.get("access_token"),
  57. "email": content.get("email"),
  58. }.items()
  59. if value not in {None, ""}
  60. }
  61. account = self.client.create_account(name=name, platform="openai", type="oauth", credentials=credentials)
  62. self._cache_account(account)
  63. return True
  64. def delete_auth_file(self, name: str) -> bool:
  65. account_id = self._name_to_id.get(name)
  66. if account_id is None:
  67. self.list_auth_files()
  68. account_id = self._name_to_id.get(name)
  69. if account_id is None:
  70. return False
  71. ok = self.client.delete_account(account_id)
  72. if ok:
  73. self._name_to_id.pop(name, None)
  74. return ok
  75. def get_auth_file(self, name: str) -> dict | None:
  76. account_id = self._name_to_id.get(name)
  77. if account_id is None:
  78. self.list_auth_files()
  79. account_id = self._name_to_id.get(name)
  80. if account_id is None:
  81. return None
  82. account = self.client.get_account(account_id)
  83. return self._content_from_account(account)
  84. def count_auth_files(self) -> int:
  85. payload = self.client.list_accounts(platform="openai", page=1, page_size=1)
  86. return int(payload.get("total") or 0) if isinstance(payload, dict) else 0