| | """Kiro Web Portal API 调用模块 |
| | |
| | 调用 Kiro 的 Web Portal API 获取用户信息,使用 CBOR 编码。 |
| | 参考: chaogei/Kiro-account-manager |
| | """ |
| | import uuid |
| | import httpx |
| | from typing import Optional, Tuple, Any, Dict |
| |
|
| | try: |
| | import cbor2 |
| | HAS_CBOR = True |
| | except ImportError: |
| | HAS_CBOR = False |
| | print("[KiroAPI] 警告: cbor2 未安装,部分功能不可用。请运行: pip install cbor2") |
| |
|
| |
|
| | |
| | KIRO_API_BASE = "https://app.kiro.dev/service/KiroWebPortalService/operation" |
| |
|
| |
|
| | async def kiro_api_request( |
| | operation: str, |
| | body: Dict[str, Any], |
| | access_token: str, |
| | idp: str = "Google", |
| | ) -> Tuple[bool, Any]: |
| | """ |
| | 调用 Kiro Web Portal API |
| | |
| | Args: |
| | operation: API 操作名称,如 "GetUserUsageAndLimits" |
| | body: 请求体(会被 CBOR 编码) |
| | access_token: Bearer token |
| | idp: 身份提供商 ("Google" 或 "Github") |
| | |
| | Returns: |
| | (success, response_data or error_dict) |
| | """ |
| | if not HAS_CBOR: |
| | return False, {"error": "cbor2 未安装"} |
| | |
| | if not access_token: |
| | return False, {"error": "缺少 access token"} |
| | |
| | url = f"{KIRO_API_BASE}/{operation}" |
| | |
| | |
| | try: |
| | encoded_body = cbor2.dumps(body) |
| | except Exception as e: |
| | return False, {"error": f"CBOR 编码失败: {e}"} |
| | |
| | headers = { |
| | "accept": "application/cbor", |
| | "content-type": "application/cbor", |
| | "smithy-protocol": "rpc-v2-cbor", |
| | "amz-sdk-invocation-id": str(uuid.uuid4()), |
| | "amz-sdk-request": "attempt=1; max=1", |
| | "x-amz-user-agent": "aws-sdk-js/1.0.0 kiro-proxy/1.0.0", |
| | "authorization": f"Bearer {access_token}", |
| | "cookie": f"Idp={idp}; AccessToken={access_token}", |
| | } |
| | |
| | try: |
| | async with httpx.AsyncClient(timeout=15, verify=False) as client: |
| | response = await client.post(url, content=encoded_body, headers=headers) |
| | |
| | if response.status_code != 200: |
| | return False, {"error": f"API 请求失败: {response.status_code}"} |
| | |
| | |
| | try: |
| | data = cbor2.loads(response.content) |
| | return True, data |
| | except Exception as e: |
| | return False, {"error": f"CBOR 解码失败: {e}"} |
| | |
| | except httpx.TimeoutException: |
| | return False, {"error": "请求超时"} |
| | except Exception as e: |
| | return False, {"error": f"请求失败: {str(e)}"} |
| |
|
| |
|
| | async def get_user_info( |
| | access_token: str, |
| | idp: str = "Google", |
| | ) -> Tuple[bool, Dict[str, Any]]: |
| | """ |
| | 获取用户信息(包括邮箱) |
| | |
| | Args: |
| | access_token: Bearer token |
| | idp: 身份提供商 ("Google" 或 "Github") |
| | |
| | Returns: |
| | (success, user_info or error_dict) |
| | user_info 包含: email, userId 等 |
| | """ |
| | success, result = await kiro_api_request( |
| | operation="GetUserUsageAndLimits", |
| | body={"isEmailRequired": True, "origin": "KIRO_IDE"}, |
| | access_token=access_token, |
| | idp=idp, |
| | ) |
| | |
| | if not success: |
| | return False, result |
| | |
| | |
| | user_info = result.get("userInfo", {}) |
| | return True, { |
| | "email": user_info.get("email"), |
| | "userId": user_info.get("userId"), |
| | "raw": result, |
| | } |
| |
|
| |
|
| | async def get_user_email( |
| | access_token: str, |
| | provider: str = "Google", |
| | ) -> Optional[str]: |
| | """ |
| | 获取用户邮箱地址 |
| | |
| | Args: |
| | access_token: Bearer token |
| | provider: 登录提供商 ("Google" 或 "Github") |
| | |
| | Returns: |
| | 邮箱地址,失败返回 None |
| | """ |
| | |
| | idp = provider |
| | if provider and provider.lower() == "google": |
| | idp = "Google" |
| | elif provider and provider.lower() == "github": |
| | idp = "Github" |
| | |
| | success, result = await get_user_info(access_token, idp) |
| | |
| | if success: |
| | return result.get("email") |
| | |
| | print(f"[KiroAPI] 获取邮箱失败: {result.get('error', '未知错误')}") |
| | return None |
| |
|