fix(aurora): harden Kling integration and surface config diagnostics

This commit is contained in:
Apple
2026-03-01 03:55:16 -08:00
parent ff97d3cf4a
commit c230abe9cf
6 changed files with 1802 additions and 0 deletions

View File

@@ -59,6 +59,9 @@ DEEPSEEK_BASE_URL=https://api.deepseek.com
# OpenAI API (optional) # OpenAI API (optional)
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Anthropic Claude API (Sofiia agent)
ANTHROPIC_API_KEY=sk-ant-api03-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Notion integration (optional) # Notion integration (optional)
NOTION_API_KEY= NOTION_API_KEY=
NOTION_VERSION=2022-06-28 NOTION_VERSION=2022-06-28
@@ -139,6 +142,14 @@ ENVIRONMENT=development
# Enable debug mode (true/false) # Enable debug mode (true/false)
DEBUG=true DEBUG=true
# -----------------------------------------------------------------------------
# Aurora / Kling Integration
# -----------------------------------------------------------------------------
KLING_ACCESS_KEY=
KLING_SECRET_KEY=
KLING_BASE_URL=https://api.klingai.com
KLING_TIMEOUT=60
# ============================================================================= # =============================================================================
# SECRET GENERATION COMMANDS # SECRET GENERATION COMMANDS
# ============================================================================= # =============================================================================

View File

@@ -94,6 +94,10 @@ services:
- AURORA_FORCE_CPU=false - AURORA_FORCE_CPU=false
- AURORA_PREFER_MPS=true - AURORA_PREFER_MPS=true
- AURORA_ENABLE_VIDEOTOOLBOX=true - AURORA_ENABLE_VIDEOTOOLBOX=true
- KLING_ACCESS_KEY=${KLING_ACCESS_KEY:-}
- KLING_SECRET_KEY=${KLING_SECRET_KEY:-}
- KLING_BASE_URL=${KLING_BASE_URL:-https://api.klingai.com}
- KLING_TIMEOUT=${KLING_TIMEOUT:-60}
volumes: volumes:
- sofiia-data:/data - sofiia-data:/data
networks: networks:

View File

@@ -0,0 +1,74 @@
# Aurora Service
`aurora-service` is a FastAPI scaffold for AISTALK media forensics workflows.
## API
- `POST /api/aurora/upload` (`multipart/form-data`)
- fields: `file`, `mode` (`tactical|forensic`)
- returns `job_id`
- `GET /api/aurora/status/{job_id}`
- `GET /api/aurora/jobs`
- `GET /api/aurora/result/{job_id}`
- `POST /api/aurora/cancel/{job_id}`
- `POST /api/aurora/delete/{job_id}`
- `GET /api/aurora/files/{job_id}/{file_name}`
## Notes
- Visual media (`video`, `photo`) run deterministic sequential enhancement with conservative defaults:
- `frame -> pre-denoise -> deblur -> face restore (GFPGAN / CodeFormer-style fallback) -> Real-ESRGAN`
- For `priority=faces`, pipeline can switch to ROI-only face processing (background preserved).
- Score-driven candidate selection is enabled for forensic face workflows.
- `audio` path remains scaffold (`Echo`) for now.
- Forensic mode adds chain-of-custody artifacts and signature metadata.
- Model weights are auto-downloaded to `AURORA_MODELS_DIR` on first run.
## Local run
```bash
cd services/aurora-service
pip install -r requirements.txt
uvicorn app.main:app --host 0.0.0.0 --port 9401
```
## Native macOS run (Apple Silicon)
```bash
cd services/aurora-service
./setup-native-macos.sh
./start-native-macos.sh
```
This profile enables:
- `AURORA_FORCE_CPU=false`
- `AURORA_PREFER_MPS=true`
- `AURORA_ENABLE_VIDEOTOOLBOX=true`
### Runtime env vars
- `AURORA_DATA_DIR` (default: `/data/aurora`)
- `AURORA_MODELS_DIR` (default: `/data/aurora/models`)
- `AURORA_FORCE_CPU` (default: `true`)
- `AURORA_PREFER_MPS` (default: `true`)
- `AURORA_ENABLE_VIDEOTOOLBOX` (default: `true`)
- `AURORA_FFMPEG_VIDEO_ENCODER` (optional override, e.g. `h264_videotoolbox`)
- `KLING_ACCESS_KEY` / `KLING_SECRET_KEY` (required for Kling endpoints)
- `KLING_BASE_URL` (default: `https://api.klingai.com`)
- `KLING_TIMEOUT` (default: `60`)
## Autostart via launchd (macOS)
```bash
cd services/aurora-service
./launchd/install-launchd.sh
```
Useful commands:
```bash
./launchd/status-launchd.sh
./launchd/uninstall-launchd.sh
```
`install-launchd.sh` bootstraps the service immediately, so reboot is not required.

View File

@@ -0,0 +1,273 @@
"""Kling AI API client for video generation and enhancement."""
from __future__ import annotations
import hashlib
import hmac
import json
import logging
import os
import time
from pathlib import Path
from typing import Any, Dict, List, Optional
import urllib.request
import urllib.error
logger = logging.getLogger(__name__)
KLING_ACCESS_KEY = os.getenv("KLING_ACCESS_KEY", "").strip()
KLING_SECRET_KEY = os.getenv("KLING_SECRET_KEY", "").strip()
KLING_BASE_URL = os.getenv("KLING_BASE_URL", "https://api.klingai.com")
KLING_TIMEOUT = int(os.getenv("KLING_TIMEOUT", "60"))
def _kling_sign(access_key: str, secret_key: str) -> str:
"""Generate Kling AI Bearer token via HMAC-SHA256 JWT-style signing."""
import base64
header = base64.urlsafe_b64encode(json.dumps({"alg": "HS256", "typ": "JWT"}).encode()).rstrip(b"=").decode()
now = int(time.time())
payload = base64.urlsafe_b64encode(json.dumps({
"iss": access_key,
"exp": now + 1800,
"nbf": now - 5,
}).encode()).rstrip(b"=").decode()
msg = f"{header}.{payload}"
sig = hmac.new(secret_key.encode(), msg.encode(), hashlib.sha256).digest()
sig_b64 = base64.urlsafe_b64encode(sig).rstrip(b"=").decode()
return f"{msg}.{sig_b64}"
def _kling_headers() -> Dict[str, str]:
if not KLING_ACCESS_KEY or not KLING_SECRET_KEY:
raise RuntimeError(
"Kling credentials are not configured. Set KLING_ACCESS_KEY and KLING_SECRET_KEY."
)
token = _kling_sign(KLING_ACCESS_KEY, KLING_SECRET_KEY)
return {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
def _kling_request(method: str, path: str, body: Optional[Dict] = None, timeout: int = KLING_TIMEOUT) -> Dict[str, Any]:
url = f"{KLING_BASE_URL}{path}"
data = json.dumps(body).encode() if body else None
req = urllib.request.Request(url, data=data, headers=_kling_headers(), method=method)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
raw = resp.read().decode("utf-8")
return json.loads(raw)
except urllib.error.HTTPError as e:
err_body = e.read().decode("utf-8", errors="replace")
raise RuntimeError(f"Kling API {method} {path}{e.code}: {err_body}") from e
def _kling_request_with_fallback(
method: str,
paths: List[str],
body: Optional[Dict] = None,
timeout: int = KLING_TIMEOUT,
) -> Dict[str, Any]:
"""Try several endpoint variants to tolerate provider path drift/gateway prefixes."""
last_error: Optional[str] = None
tried: List[str] = []
for path in paths:
tried.append(path)
try:
return _kling_request(method, path, body=body, timeout=timeout)
except RuntimeError as e:
msg = str(e)
last_error = msg
# 404 likely means wrong endpoint path; try next candidate.
if "→ 404:" in msg:
continue
# Non-404 errors are usually actionable immediately.
raise
raise RuntimeError(
f"Kling API endpoint mismatch for {method}. Tried: {tried}. Last error: {last_error or 'unknown'}"
)
# ── Video Enhancement (Video-to-Video) ──────────────────────────────────────
def kling_video_enhance(
*,
video_url: Optional[str] = None,
video_id: Optional[str] = None,
prompt: str = "",
negative_prompt: str = "noise, blur, artifacts",
mode: str = "pro",
duration: str = "5",
cfg_scale: float = 0.5,
callback_url: Optional[str] = None,
) -> Dict[str, Any]:
"""Submit a video-to-video enhancement task to Kling AI.
Args:
video_url: Direct URL to input video.
video_id: Kling resource ID for previously uploaded video.
prompt: Text guidance for enhancement.
negative_prompt: Things to avoid.
mode: 'std' or 'pro'.
duration: '5' or '10' seconds.
cfg_scale: 0.0-1.0, how strongly to follow prompt.
callback_url: Webhook for completion notification.
Returns:
Task response dict with task_id.
"""
if not video_url and not video_id:
raise ValueError("Either video_url or video_id must be provided")
payload: Dict[str, Any] = {
"model": f"kling-v1-5",
"mode": mode,
"duration": duration,
"cfg_scale": cfg_scale,
"prompt": prompt,
"negative_prompt": negative_prompt,
}
if video_url:
payload["video_url"] = video_url
if video_id:
payload["video_id"] = video_id
if callback_url:
payload["callback_url"] = callback_url
return _kling_request_with_fallback(
"POST",
["/v1/videos/video2video", "/kling/v1/videos/video2video"],
body=payload,
)
def kling_video_generate(
*,
image_url: Optional[str] = None,
image_id: Optional[str] = None,
prompt: str,
negative_prompt: str = "noise, blur, artifacts, distortion",
model: str = "kling-v1-5",
mode: str = "pro",
duration: str = "5",
cfg_scale: float = 0.5,
aspect_ratio: str = "16:9",
callback_url: Optional[str] = None,
) -> Dict[str, Any]:
"""Generate video from image + prompt (image-to-video).
Args:
image_url: Source still frame URL.
image_id: Kling resource ID for previously uploaded image.
prompt: Animation guidance.
model: 'kling-v1', 'kling-v1-5', 'kling-v1-6'.
mode: 'std' or 'pro'.
duration: '5' or '10'.
aspect_ratio: '16:9', '9:16', '1:1'.
"""
if not image_url and not image_id:
raise ValueError("Either image_url or image_id must be provided")
payload: Dict[str, Any] = {
"model": model,
"mode": mode,
"duration": duration,
"cfg_scale": cfg_scale,
"prompt": prompt,
"negative_prompt": negative_prompt,
"aspect_ratio": aspect_ratio,
}
if image_url:
payload["image"] = {"type": "url", "url": image_url}
if image_id:
payload["image"] = {"type": "id", "id": image_id}
if callback_url:
payload["callback_url"] = callback_url
return _kling_request_with_fallback(
"POST",
["/v1/videos/image2video", "/kling/v1/videos/image2video"],
body=payload,
)
def kling_task_status(task_id: str) -> Dict[str, Any]:
"""Get status of any Kling task by ID."""
return _kling_request_with_fallback(
"GET",
[f"/v1/tasks/{task_id}", f"/kling/v1/tasks/{task_id}"],
)
def kling_video_task_status(task_id: str, endpoint: str = "video2video") -> Dict[str, Any]:
"""Get status of a video task."""
return _kling_request_with_fallback(
"GET",
[f"/v1/videos/{endpoint}/{task_id}", f"/kling/v1/videos/{endpoint}/{task_id}"],
)
def kling_list_models() -> Dict[str, Any]:
"""List available Kling models."""
return _kling_request_with_fallback(
"GET",
["/v1/models", "/kling/v1/models"],
)
def kling_upload_file(file_path: Path) -> Dict[str, Any]:
"""Upload a local file to Kling storage and return resource_id."""
import base64
with open(file_path, "rb") as f:
data = f.read()
b64 = base64.b64encode(data).decode()
suffix = file_path.suffix.lstrip(".").lower()
mime_map = {
"mp4": "video/mp4", "mov": "video/quicktime", "avi": "video/x-msvideo",
"jpg": "image/jpeg", "jpeg": "image/jpeg", "png": "image/png",
}
mime = mime_map.get(suffix, "application/octet-stream")
payload = {
"file": b64,
"file_name": file_path.name,
"content_type": mime,
}
return _kling_request_with_fallback(
"POST",
["/v1/files/upload", "/v1/files", "/kling/v1/files/upload", "/kling/v1/files"],
body=payload,
timeout=120,
)
def kling_poll_until_done(
task_id: str,
endpoint: str = "video2video",
max_wait_sec: int = 600,
poll_interval: int = 5,
) -> Dict[str, Any]:
"""Poll Kling task until completed/failed or timeout."""
start = time.time()
while True:
status_resp = kling_video_task_status(task_id, endpoint)
task = status_resp.get("data", {})
state = task.get("task_status") or task.get("status") or "processing"
if state in ("succeed", "completed", "failed", "error"):
return status_resp
elapsed = time.time() - start
if elapsed >= max_wait_sec:
raise TimeoutError(f"Kling task {task_id} timed out after {max_wait_sec}s (last status: {state})")
logger.debug("Kling task %s status=%s elapsed=%.0fs", task_id, state, elapsed)
time.sleep(poll_interval)
def kling_health_check() -> Dict[str, Any]:
"""Quick connectivity check — returns status dict."""
try:
resp = _kling_request("GET", "/v1/models", timeout=10)
return {"ok": True, "models": resp}
except Exception as exc:
return {"ok": False, "error": str(exc)}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,113 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
LABEL="${AURORA_LAUNCHD_LABEL:-com.daarion.aurora}"
DOMAIN="gui/$(id -u)"
LAUNCH_AGENTS_DIR="${HOME}/Library/LaunchAgents"
PLIST_PATH="${LAUNCH_AGENTS_DIR}/${LABEL}.plist"
START_SCRIPT="${ROOT_DIR}/start-native-macos.sh"
PORT_VALUE="${PORT:-9401}"
DATA_DIR_VALUE="${AURORA_DATA_DIR:-${HOME}/.sofiia/aurora-data}"
MODELS_DIR_VALUE="${AURORA_MODELS_DIR:-${DATA_DIR_VALUE}/models}"
PUBLIC_BASE_URL_VALUE="${AURORA_PUBLIC_BASE_URL:-http://127.0.0.1:${PORT_VALUE}}"
CORS_ORIGINS_VALUE="${AURORA_CORS_ORIGINS:-*}"
FORCE_CPU_VALUE="${AURORA_FORCE_CPU:-false}"
PREFER_MPS_VALUE="${AURORA_PREFER_MPS:-true}"
ENABLE_VTB_VALUE="${AURORA_ENABLE_VIDEOTOOLBOX:-true}"
KLING_ACCESS_KEY_VALUE="${KLING_ACCESS_KEY:-}"
KLING_SECRET_KEY_VALUE="${KLING_SECRET_KEY:-}"
KLING_BASE_URL_VALUE="${KLING_BASE_URL:-https://api.klingai.com}"
KLING_TIMEOUT_VALUE="${KLING_TIMEOUT:-60}"
LOG_DIR="${DATA_DIR_VALUE}/logs"
LOG_OUT="${LOG_DIR}/launchd.out.log"
LOG_ERR="${LOG_DIR}/launchd.err.log"
PATH_VALUE="${PATH:-/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin}"
if [ ! -x "${START_SCRIPT}" ]; then
echo "[aurora-launchd] missing start script: ${START_SCRIPT}"
exit 1
fi
if [ ! -x "${ROOT_DIR}/.venv-macos/bin/python" ]; then
echo "[aurora-launchd] .venv-macos is missing. Run ./setup-native-macos.sh first."
exit 1
fi
mkdir -p "${LAUNCH_AGENTS_DIR}" "${LOG_DIR}" "${DATA_DIR_VALUE}" "${MODELS_DIR_VALUE}"
cat > "${PLIST_PATH}" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>${LABEL}</string>
<key>ProgramArguments</key>
<array>
<string>${START_SCRIPT}</string>
</array>
<key>WorkingDirectory</key>
<string>${ROOT_DIR}</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>${LOG_OUT}</string>
<key>StandardErrorPath</key>
<string>${LOG_ERR}</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>${PATH_VALUE}</string>
<key>PYTHONUNBUFFERED</key>
<string>1</string>
<key>PORT</key>
<string>${PORT_VALUE}</string>
<key>AURORA_DATA_DIR</key>
<string>${DATA_DIR_VALUE}</string>
<key>AURORA_MODELS_DIR</key>
<string>${MODELS_DIR_VALUE}</string>
<key>AURORA_PUBLIC_BASE_URL</key>
<string>${PUBLIC_BASE_URL_VALUE}</string>
<key>AURORA_CORS_ORIGINS</key>
<string>${CORS_ORIGINS_VALUE}</string>
<key>AURORA_FORCE_CPU</key>
<string>${FORCE_CPU_VALUE}</string>
<key>AURORA_PREFER_MPS</key>
<string>${PREFER_MPS_VALUE}</string>
<key>AURORA_ENABLE_VIDEOTOOLBOX</key>
<string>${ENABLE_VTB_VALUE}</string>
<key>KLING_ACCESS_KEY</key>
<string>${KLING_ACCESS_KEY_VALUE}</string>
<key>KLING_SECRET_KEY</key>
<string>${KLING_SECRET_KEY_VALUE}</string>
<key>KLING_BASE_URL</key>
<string>${KLING_BASE_URL_VALUE}</string>
<key>KLING_TIMEOUT</key>
<string>${KLING_TIMEOUT_VALUE}</string>
</dict>
</dict>
</plist>
PLIST
chmod 644 "${PLIST_PATH}"
launchctl bootout "${DOMAIN}/${LABEL}" >/dev/null 2>&1 || true
launchctl bootstrap "${DOMAIN}" "${PLIST_PATH}"
launchctl enable "${DOMAIN}/${LABEL}" >/dev/null 2>&1 || true
launchctl kickstart -k "${DOMAIN}/${LABEL}"
echo "[aurora-launchd] installed: ${PLIST_PATH}"
echo "[aurora-launchd] active label: ${DOMAIN}/${LABEL}"
echo "[aurora-launchd] logs: ${LOG_OUT} | ${LOG_ERR}"
echo "[aurora-launchd] check: launchctl print ${DOMAIN}/${LABEL}"