This commit is contained in:
2026-04-12 01:06:31 +07:00
commit 10d660cbcb
1066 changed files with 228596 additions and 0 deletions

View File

@@ -0,0 +1,189 @@
#!/usr/bin/env python3
"""
MiniMax API client - shared HTTP utilities for all MiniMax generation tasks.
Handles authentication, API calls, async task polling, and file downloads.
Base URL: https://api.minimax.io/v1
Auth: Bearer token via MINIMAX_API_KEY environment variable.
"""
import json
import os
import sys
import time
from pathlib import Path
from typing import Dict, Any, Optional
try:
import requests
except ImportError:
print("Error: requests package not installed")
print("Install with: pip install requests")
sys.exit(1)
# Import centralized environment resolver
CLAUDE_ROOT = Path(__file__).parent.parent.parent.parent
sys.path.insert(0, str(CLAUDE_ROOT / 'scripts'))
try:
from resolve_env import resolve_env
CENTRALIZED_RESOLVER_AVAILABLE = True
except ImportError:
CENTRALIZED_RESOLVER_AVAILABLE = False
BASE_URL = "https://api.minimax.io/v1"
def find_minimax_api_key() -> Optional[str]:
"""Find MINIMAX_API_KEY using centralized resolver or environment."""
if CENTRALIZED_RESOLVER_AVAILABLE:
return resolve_env('MINIMAX_API_KEY', skill='ai-multimodal')
# Fallback: check environment and .env files
api_key = os.getenv('MINIMAX_API_KEY')
if api_key:
return api_key
# Check .env files in skill directory hierarchy
try:
from dotenv import load_dotenv
skill_dir = Path(__file__).parent.parent
for env_path in [skill_dir / '.env', skill_dir.parent / '.env']:
if env_path.exists():
load_dotenv(env_path, override=True)
api_key = os.getenv('MINIMAX_API_KEY')
if api_key:
return api_key
except ImportError:
pass
return None
def get_headers(api_key: str) -> Dict[str, str]:
"""Build authorization headers for MiniMax API."""
return {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
def api_post(endpoint: str, payload: Dict[str, Any], api_key: str,
verbose: bool = False, timeout: int = 120) -> Dict[str, Any]:
"""Make POST request to MiniMax API with error handling."""
url = f"{BASE_URL}/{endpoint}"
headers = get_headers(api_key)
if verbose:
print(f" POST {url}", file=sys.stderr)
response = requests.post(url, headers=headers, json=payload, timeout=timeout)
if response.status_code != 200:
raise Exception(
f"MiniMax API error (HTTP {response.status_code}): {response.text}"
)
data = response.json()
# Check MiniMax-specific error codes
base_resp = data.get("base_resp", {})
status_code = base_resp.get("status_code", 0)
if status_code != 0:
raise Exception(
f"MiniMax API error (code {status_code}): "
f"{base_resp.get('status_msg', 'Unknown error')}"
)
return data
def api_get(endpoint: str, params: Dict[str, str], api_key: str,
verbose: bool = False) -> Dict[str, Any]:
"""Make GET request to MiniMax API."""
url = f"{BASE_URL}/{endpoint}"
headers = get_headers(api_key)
if verbose:
print(f" GET {url}", file=sys.stderr)
response = requests.get(url, headers=headers, params=params, timeout=60)
if response.status_code != 200:
raise Exception(
f"MiniMax API error (HTTP {response.status_code}): {response.text}"
)
return response.json()
def poll_async_task(task_id: str, task_type: str, api_key: str,
poll_interval: int = 10, max_wait: int = 600,
verbose: bool = False) -> Dict[str, Any]:
"""Poll async task (video/music) until completion.
Args:
task_id: The task ID returned from creation endpoint
task_type: 'video_generation' or 'music_generation'
poll_interval: Seconds between polls (default 10)
max_wait: Maximum wait time in seconds (default 600)
"""
elapsed = 0
while elapsed < max_wait:
result = api_get(
f"query/{task_type}",
{"task_id": task_id},
api_key,
verbose=False
)
status = result.get("status", "Unknown")
if verbose and elapsed > 0 and elapsed % 30 == 0:
print(f" Polling... {elapsed}s elapsed, status: {status}",
file=sys.stderr)
if status == "Success":
return result
elif status in ("Failed", "Error"):
raise Exception(f"Task failed: {json.dumps(result)}")
time.sleep(poll_interval)
elapsed += poll_interval
raise TimeoutError(f"Task {task_id} timed out after {max_wait}s")
def download_file(file_id: str, api_key: str, output_path: str,
verbose: bool = False) -> str:
"""Download file from MiniMax file service."""
result = api_get("files/retrieve", {"file_id": file_id}, api_key, verbose)
download_url = result.get("file", {}).get("download_url")
if not download_url:
raise Exception(f"No download URL in response: {json.dumps(result)}")
if verbose:
print(f" Downloading to: {output_path}", file=sys.stderr)
response = requests.get(download_url, stream=True, timeout=300)
response.raise_for_status()
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
with open(output_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
return output_path
def get_output_dir() -> Path:
"""Get project output directory for generated assets."""
script_dir = Path(__file__).parent
for parent in [script_dir] + list(script_dir.parents):
if (parent / '.git').exists() or (parent / '.claude').exists():
output_dir = parent / 'docs' / 'assets'
output_dir.mkdir(parents=True, exist_ok=True)
return output_dir
# Fallback
output_dir = script_dir.parent / 'assets'
output_dir.mkdir(parents=True, exist_ok=True)
return output_dir