feat(api): fix: DPLAN-0049 waves 1-2 — help_text, silent_catch, commented_logger

Co-Authored-By: @api <api@aipass>
This commit is contained in:
AIOSAI
2026-03-22 23:01:24 -07:00
co-authored by @api
parent 123455c107
commit 19e63ae6df
18 changed files with 169 additions and 138 deletions
+1 -1
View File
@@ -272,7 +272,7 @@ def main():
return 0
else:
logger.warning(f"Unknown command: {command}")
error(f"Unknown command: {command}", suggestion="Run 'python3 api.py --help' for available commands")
error(f"Unknown command: {command}", suggestion="Run 'drone @api --help' for available commands")
return 1
if __name__ == "__main__":
+2
View File
@@ -93,6 +93,7 @@ def read_env_file(env_var: str, search_paths: Optional[List[Path]] = None) -> Op
return value.strip()
except Exception as e:
# Error reading env_file
logger.warning(f"Error reading env file '{env_file}': {e}")
continue
# Variable not found in any .env file
@@ -136,6 +137,7 @@ def read_env_file_dict(env_path: Path) -> Dict[str, str]:
except Exception as e:
# Error reading env_path
logger.error(f"Error reading env file '{env_path}': {e}")
return env_dict
@@ -28,6 +28,9 @@ import sys
import os
from typing import Optional, Dict, Any
# Logging
from aipass.prax import logger
# Internal handlers
from aipass.api.apps.handlers.auth.env import read_env_file
@@ -121,6 +124,7 @@ def get_api_key(provider: str = "openrouter") -> Optional[str]:
except Exception as e:
# Failed to get key
logger.error(f"Failed to get API key for provider '{provider}': {e}")
return None
@@ -163,6 +167,7 @@ def get_key_from_config(provider: str) -> Optional[str]:
except Exception as e:
# Error reading config
logger.error(f"Error reading config for provider '{provider}': {e}")
return None
@@ -128,10 +128,10 @@ def load_provider_config(provider: str = "openrouter") -> Optional[Dict[str, Any
return None
except json.JSONDecodeError as e:
# Invalid JSON in config file
logger.error(f"Invalid JSON in provider config: {e}")
return None
except Exception as e:
# Failed to load provider config
logger.error(f"Failed to load provider config: {e}")
return None
@@ -153,7 +153,7 @@ def get_full_config() -> Optional[Dict[str, Any]]:
return json.load(f)
except Exception as e:
# Failed to load full config
logger.error(f"Failed to load full config: {e}")
return None
@@ -258,7 +258,7 @@ def update_full_config(updates: Dict[str, Any]) -> bool:
return True
except Exception as e:
# Failed to update full config
logger.error(f"Failed to update full config: {e}")
return False
@@ -340,7 +340,7 @@ def _create_default_config() -> bool:
return True
except Exception as e:
# Failed to create default config
logger.error(f"Failed to create default config: {e}")
return False
+11 -4
View File
@@ -23,6 +23,9 @@ Consumers get authenticated credentials, they decide what to do with them.
from pathlib import Path
from typing import Optional
# Logging
from aipass.prax import logger
# JSON handler
from aipass.api.apps.handlers.json import json_handler
@@ -51,7 +54,8 @@ try:
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow
GOOGLE_AUTH_AVAILABLE = True
except ImportError:
except ImportError as e:
logger.warning(f"Google auth libraries not available: {e}")
GOOGLE_AUTH_AVAILABLE = False
Credentials = None # type: ignore[assignment, misc]
Request = None # type: ignore[assignment, misc]
@@ -90,7 +94,8 @@ def load_credentials(scopes: Optional[list] = None) -> Optional["Credentials"]:
creds = Credentials.from_authorized_user_file(str(CREDS_PATH), effective_scopes)
json_handler.log_operation("credentials_loaded", {"source": str(CREDS_PATH)})
return creds
except Exception:
except Exception as e:
logger.error(f"Failed to load credentials from {CREDS_PATH}: {e}")
return None
@@ -113,7 +118,8 @@ def refresh_credentials(creds: "Credentials") -> bool:
creds.refresh(Request())
_save_credentials(creds)
return True
except Exception:
except Exception as e:
logger.error(f"Failed to refresh credentials: {e}")
return False
@@ -150,7 +156,8 @@ def run_oauth_flow(
creds = flow.run_local_server(port=port, open_browser=open_browser)
_save_credentials(creds)
return creds
except Exception:
except Exception as e:
logger.error(f"OAuth flow failed: {e}")
return None
@@ -5,6 +5,7 @@
# Created: 2026-03-14
# Modified: 2026-03-14
# =============================================
# pyright: reportMissingImports=false, reportOptionalCall=false
"""
Google API Service Factory
@@ -30,6 +31,7 @@ Usage:
from typing import Optional
from aipass.prax import logger
from aipass.api.apps.handlers.google import auth as auth
from aipass.api.apps.handlers.json import json_handler
@@ -40,7 +42,8 @@ from aipass.api.apps.handlers.json import json_handler
try:
from googleapiclient.discovery import build
GOOGLE_BUILD_AVAILABLE = True
except ImportError:
except ImportError as e:
logger.warning(f"Google API client library not available: {e}")
GOOGLE_BUILD_AVAILABLE = False
build = None # type: ignore[assignment]
@@ -78,7 +81,8 @@ def build_service(
service = build(service_name, version, credentials=creds)
json_handler.log_operation("build_service", {"service": service_name, "version": version})
return service
except Exception:
except Exception as e:
logger.error(f"Failed to build Google {service_name} service: {e}")
return None
@@ -118,5 +122,6 @@ def build_thread_safe_service(
try:
return build(service_name, version, credentials=creds)
except Exception:
except Exception as e:
logger.error(f"Failed to build thread-safe Google {service_name} service: {e}")
return None
@@ -13,6 +13,9 @@ from typing import Dict, Any, Optional
import sys
import inspect
# Logging
from aipass.prax import logger
# Infrastructure
# Constants — package-relative paths
@@ -42,7 +45,8 @@ def _get_caller_module_name() -> str:
# Fallback
return "unknown"
except Exception:
except Exception as e:
logger.warning(f"Failed to detect module name: {e}")
return "unknown"
@@ -118,8 +122,8 @@ def ensure_json_exists(module_name: str, json_type: str) -> bool:
return True
else:
pass # Corrupted - regenerating
except Exception:
pass # Unreadable - regenerating
except Exception as e:
logger.warning(f"Unreadable JSON at {json_path}, regenerating: {e}")
template = _create_default(json_type, module_name)
@@ -138,7 +142,8 @@ def load_json(module_name: str, json_type: str) -> Optional[Any]:
try:
with open(json_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception:
except Exception as e:
logger.error(f"Failed to load JSON from {json_path}: {e}")
return None
@@ -156,7 +161,8 @@ def save_json(module_name: str, json_type: str, data: Any) -> bool:
with open(json_path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
return True
except Exception:
except Exception as e:
logger.error(f"Failed to save JSON to {json_path}: {e}")
return False
@@ -27,6 +27,9 @@ from pathlib import Path
import inspect
from typing import Dict, Any, Optional, Tuple
# Logging
from aipass.prax import logger
# JSON handler
from aipass.api.apps.handlers.json import json_handler
@@ -76,11 +79,11 @@ def get_caller_info() -> Optional[Dict[str, Any]]:
json_handler.log_operation("caller_detected", {"caller": result.get("caller_name"), "category": "skills"})
return result
# logger.info(f"[{MODULE_NAME}] Could not detect caller from stack trace")
logger.info(f"[{MODULE_NAME}] Could not detect caller from stack trace")
return None
except Exception as e:
# logger.error(f"[{MODULE_NAME}] Caller detection failed: {e}")
logger.error(f"Caller detection failed: {e}")
return None
@@ -124,17 +127,17 @@ def get_json_folder_path(caller: str) -> Optional[Path]:
base_path = _PACKAGE_ROOT / "skills" / f"skills_{skills_category}"
json_folder = base_path / f"{skills_category}_json"
else:
# logger.info(f"[{MODULE_NAME}] Cannot parse skills category from: {caller}")
logger.info(f"[{MODULE_NAME}] Cannot parse skills category from: {caller}")
return None
else:
# logger.info(f"[{MODULE_NAME}] Unknown caller pattern: {caller}")
logger.info(f"[{MODULE_NAME}] Unknown caller pattern: {caller}")
return None
# logger.info(f"[{MODULE_NAME}] Resolved JSON folder for {caller}: {json_folder}")
logger.info(f"[{MODULE_NAME}] Resolved JSON folder for {caller}: {json_folder}")
return json_folder
except Exception as e:
# logger.error(f"[{MODULE_NAME}] Failed to determine JSON folder for {caller}: {e}")
logger.error(f"Failed to determine JSON folder for {caller}: {e}")
return None
@@ -153,7 +156,7 @@ def detect_caller_category(caller_path: Path) -> str:
return "unknown"
except Exception as e:
# logger.error(f"[{MODULE_NAME}] Failed to detect category for {caller_path}: {e}")
logger.error(f"Failed to detect category for {caller_path}: {e}")
return "unknown"
@@ -169,7 +172,7 @@ def _detect_flow_caller(frame_path: Path) -> Dict[str, Any]:
json_folder_path = flow_path / "flow_json"
caller_name = frame_path.stem
# logger.info(f"[{MODULE_NAME}] Detected flow caller: {caller_name}")
logger.info(f"[{MODULE_NAME}] Detected flow caller: {caller_name}")
return {
"caller_name": caller_name,
@@ -180,7 +183,7 @@ def _detect_flow_caller(frame_path: Path) -> Dict[str, Any]:
}
except Exception as e:
# logger.error(f"[{MODULE_NAME}] Failed to detect flow caller: {e}")
logger.error(f"Failed to detect flow caller: {e}")
return _create_fallback_info(frame_path)
@@ -192,7 +195,7 @@ def _detect_prax_caller(frame_path: Path) -> Dict[str, Any]:
json_folder_path = prax_path / "prax_json"
caller_name = frame_path.stem
# logger.info(f"[{MODULE_NAME}] Detected prax caller: {caller_name}")
logger.info(f"[{MODULE_NAME}] Detected prax caller: {caller_name}")
return {
"caller_name": caller_name,
@@ -203,7 +206,7 @@ def _detect_prax_caller(frame_path: Path) -> Dict[str, Any]:
}
except Exception as e:
# logger.error(f"[{MODULE_NAME}] Failed to detect prax caller: {e}")
logger.error(f"Failed to detect prax caller: {e}")
return _create_fallback_info(frame_path)
@@ -220,7 +223,7 @@ def _detect_skills_caller(frame_path: Path) -> Dict[str, Any]:
json_folder_path = skills_path / f"{category}_json"
caller_name = frame_path.stem
# logger.info(f"[{MODULE_NAME}] Detected skills caller: {caller_name} (category: {category})")
logger.info(f"[{MODULE_NAME}] Detected skills caller: {caller_name} (category: {category})")
return {
"caller_name": caller_name,
@@ -231,11 +234,11 @@ def _detect_skills_caller(frame_path: Path) -> Dict[str, Any]:
"detection_method": "stack"
}
# logger.info(f"[{MODULE_NAME}] Could not find skills directory in path: {frame_path}")
logger.info(f"[{MODULE_NAME}] Could not find skills directory in path: {frame_path}")
return _create_fallback_info(frame_path)
except Exception as e:
# logger.error(f"[{MODULE_NAME}] Failed to detect skills caller: {e}")
logger.error(f"Failed to detect skills caller: {e}")
return _create_fallback_info(frame_path)
@@ -244,7 +247,7 @@ def _create_fallback_info(frame_path: Path) -> Dict[str, Any]:
caller_name = frame_path.stem
category = detect_caller_category(frame_path)
# logger.info(f"[{MODULE_NAME}] Using fallback detection for: {caller_name}")
logger.info(f"[{MODULE_NAME}] Using fallback detection for: {caller_name}")
return {
"caller_name": caller_name,
@@ -265,26 +268,26 @@ def validate_caller_info(caller_info: Dict[str, Any]) -> bool:
required_fields = ["caller_name", "caller_path", "category", "detection_method"]
for field in required_fields:
if field not in caller_info:
# logger.info(f"[{MODULE_NAME}] Missing required field: {field}")
logger.info(f"[{MODULE_NAME}] Missing required field: {field}")
return False
if not caller_info["caller_name"]:
# logger.info(f"[{MODULE_NAME}] Caller name is empty")
logger.info(f"[{MODULE_NAME}] Caller name is empty")
return False
if not isinstance(caller_info["caller_path"], Path):
# logger.info(f"[{MODULE_NAME}] Caller path is not a Path object")
logger.info(f"[{MODULE_NAME}] Caller path is not a Path object")
return False
valid_categories = ["flow", "prax", "skills", "unknown"]
if caller_info["category"] not in valid_categories:
# logger.info(f"[{MODULE_NAME}] Invalid category: {caller_info['category']}")
logger.info(f"[{MODULE_NAME}] Invalid category: {caller_info['category']}")
return False
return True
except Exception as e:
# logger.error(f"[{MODULE_NAME}] Validation failed: {e}")
logger.error(f"Validation failed: {e}")
return False
@@ -294,7 +297,6 @@ def validate_caller_info(caller_info: Dict[str, Any]) -> bool:
def _initialize():
"""Initialize caller detection module."""
# logger.info(f"[{MODULE_NAME}] Caller detection handler loaded (v{MODULE_VERSION})")
pass
logger.info(f"[{MODULE_NAME}] Caller detection handler loaded (v{MODULE_VERSION})")
_initialize()
@@ -48,8 +48,8 @@ from aipass.prax import logger
try:
from openai import OpenAI
OPENAI_AVAILABLE = True
except ImportError:
# logger.error("OpenAI SDK not available. Install with: pip install openai")
except ImportError as e:
logger.error(f"OpenAI SDK not available. Install with: pip install openai: {e}")
OpenAI = None # type: ignore[assignment,misc]
OPENAI_AVAILABLE = False
@@ -121,7 +121,7 @@ def create_client(api_key: str, base_url: str = OPENROUTER_BASE_URL, timeout: in
default_headers=OPENROUTER_HEADERS
)
# logger.info(f"Created OpenRouter client - base_url: {base_url}, timeout: {timeout}s")
logger.info(f"Created OpenRouter client - base_url: {base_url}, timeout: {timeout}s")
json_handler.log_operation("client_initialized", {"base_url": base_url, "timeout": timeout})
return client
@@ -155,7 +155,7 @@ def get_cached_client(api_key: str, base_url: str = OPENROUTER_BASE_URL, timeout
cached_client = _client_cache[api_key]
# Verify cached client is still valid
if cached_client and cached_client.api_key == api_key:
# logger.info("Using cached OpenRouter client")
logger.info("Using cached OpenRouter client")
return cached_client
# Create new client
@@ -169,10 +169,10 @@ def get_cached_client(api_key: str, base_url: str = OPENROUTER_BASE_URL, timeout
# Remove oldest client (first key in dict)
oldest_key = next(iter(_client_cache))
del _client_cache[oldest_key]
# logger.info(f"Removed oldest cached client - cache limit: {MAX_CACHED_CLIENTS}")
logger.info(f"Removed oldest cached client - cache limit: {MAX_CACHED_CLIENTS}")
_client_cache[api_key] = client
# logger.info("Cached new OpenRouter client")
logger.info("Cached new OpenRouter client")
return client
@@ -257,7 +257,7 @@ def extract_response(response: Any) -> Optional[Dict[str, Any]]:
content = response.choices[0].message.content
if not content:
# logger.warning("Response has no content")
logger.warning("Response has no content")
return None
# Extract metadata
@@ -268,11 +268,11 @@ def extract_response(response: Any) -> Optional[Dict[str, Any]]:
"finish_reason": response.choices[0].finish_reason if hasattr(response.choices[0], 'finish_reason') else None
}
# logger.info(f"Extracted response - length: {len(content)} chars, id: {result['id']}")
logger.info(f"Extracted response - length: {len(content)} chars, id: {result['id']}")
return result
except Exception as e:
# logger.error(f"Failed to extract response: {e}")
logger.error(f"Failed to extract response: {e}")
return None
@@ -311,16 +311,16 @@ def get_response(prompt: str, caller: Optional[str] = None, model: Optional[str]
caller_info = get_caller_info()
if caller_info and caller_info.get("caller_name"):
caller = caller_info["caller_name"]
# logger.info(f"Auto-detected caller: {caller}")
logger.info(f"Auto-detected caller: {caller}")
else:
# logger.warning("Could not detect caller - using 'unknown'")
logger.warning("Could not detect caller - using 'unknown'")
caller = "unknown"
# Step 1b: Ensure caller has config (auto-provision if missing)
try:
ensure_caller_config(caller)
except Exception:
pass # Provisioning is best-effort — don't block the API call
except Exception as e:
logger.warning(f"Caller config provisioning failed (non-blocking): {e}")
# Step 2: Require model from caller - no defaults
if not model:
@@ -362,11 +362,9 @@ def get_response(prompt: str, caller: Optional[str] = None, model: Optional[str]
try:
track_usage(result["id"], caller if caller else "unknown", model, api_key)
except Exception as e:
# logger.warning(f"Usage tracking failed: {e}")
# Don't fail the request if tracking fails
pass
logger.warning(f"Usage tracking failed: {e}")
# logger.info(f"Successfully got response - caller: {caller}, model: {model}, length: {len(result['content'])} chars")
logger.info(f"Successfully got response - caller: {caller}, model: {model}, length: {len(result['content'])} chars")
return result
@@ -382,7 +380,6 @@ def clear_client_cache() -> None:
global _client_cache
count = len(_client_cache)
_client_cache.clear()
# logger.info(f"Cleared {count} cached clients")
logger.info(f"Cleared {count} cached OpenRouter clients")
@@ -74,7 +74,7 @@ def get_available_models(api_key: Optional[str] = None) -> List[Dict]:
api_key = get_api_key("openrouter")
if not api_key:
# logger.info(f"[{MODULE_NAME}] No API key available for OpenRouter")
logger.info(f"[{MODULE_NAME}] No API key available for OpenRouter")
logger.warning("No OpenRouter API key found")
return []
@@ -82,15 +82,15 @@ def get_available_models(api_key: Optional[str] = None) -> List[Dict]:
models = fetch_models_from_api(api_key)
if models:
# logger.info(f"[{MODULE_NAME}] Fetched {len(models)} models from OpenRouter")
logger.info(f"[{MODULE_NAME}] Fetched {len(models)} models from OpenRouter")
json_handler.log_operation("models_listed", {"count": len(models)})
return models
else:
# logger.info(f"[{MODULE_NAME}] No models returned from API")
logger.info(f"[{MODULE_NAME}] No models returned from API")
return []
except Exception as e:
# logger.info(f"[{MODULE_NAME}] Failed to get available models: {e}")
logger.info(f"[{MODULE_NAME}] Failed to get available models: {e}")
logger.error(f"Error fetching models: {e}")
return []
@@ -123,11 +123,11 @@ def get_free_models(api_key: Optional[str] = None) -> List[Dict]:
# Filter for free models
free_models = filter_by_pricing(all_models, max_cost=0.0)
# logger.info(f"[{MODULE_NAME}] Found {len(free_models)} free models")
logger.info(f"[{MODULE_NAME}] Found {len(free_models)} free models")
return free_models
except Exception as e:
# logger.info(f"[{MODULE_NAME}] Failed to get free models: {e}")
logger.info(f"[{MODULE_NAME}] Failed to get free models: {e}")
logger.error(f"Error fetching free models: {e}")
return []
@@ -156,7 +156,7 @@ def fetch_models_from_api(api_key: str) -> List[Dict]:
}
# Make API request
# logger.info(f"[{MODULE_NAME}] Requesting models from OpenRouter API")
logger.info(f"[{MODULE_NAME}] Requesting models from OpenRouter API")
response = requests.get(
OPENROUTER_API_URL,
headers=headers,
@@ -165,7 +165,7 @@ def fetch_models_from_api(api_key: str) -> List[Dict]:
# Check response status
if response.status_code != 200:
# logger.info(f"[{MODULE_NAME}] API request failed with status {response.status_code}")
logger.info(f"[{MODULE_NAME}] API request failed with status {response.status_code}")
logger.error(f"OpenRouter API error: {response.status_code}")
return []
@@ -175,29 +175,29 @@ def fetch_models_from_api(api_key: str) -> List[Dict]:
# Extract models from response
if "data" in data and isinstance(data["data"], list):
models = data["data"]
# logger.info(f"[{MODULE_NAME}] Successfully parsed {len(models)} models")
logger.info(f"[{MODULE_NAME}] Successfully parsed {len(models)} models")
return models
else:
# logger.info(f"[{MODULE_NAME}] Invalid response format - no 'data' field")
logger.info(f"[{MODULE_NAME}] Invalid response format - no 'data' field")
return []
except requests.exceptions.Timeout:
# logger.info(f"[{MODULE_NAME}] API request timeout after {DEFAULT_TIMEOUT}s")
logger.info(f"[{MODULE_NAME}] API request timeout after {DEFAULT_TIMEOUT}s")
logger.error("Request timeout - OpenRouter API not responding")
return []
except requests.exceptions.RequestException as e:
# logger.info(f"[{MODULE_NAME}] Network error: {e}")
logger.info(f"[{MODULE_NAME}] Network error: {e}")
logger.error(f"Network error: {e}")
return []
except ValueError as e:
# logger.info(f"[{MODULE_NAME}] JSON parse error: {e}")
logger.info(f"[{MODULE_NAME}] JSON parse error: {e}")
logger.error("Invalid JSON response from API")
return []
except Exception as e:
# logger.info(f"[{MODULE_NAME}] Unexpected error fetching models: {e}")
logger.info(f"[{MODULE_NAME}] Unexpected error fetching models: {e}")
logger.error(f"Error: {e}")
return []
@@ -232,19 +232,20 @@ def filter_by_pricing(models: List[Dict], max_cost: float = 0.0) -> List[Dict]:
try:
prompt_cost = float(pricing.get("prompt", "0"))
completion_cost = float(pricing.get("completion", "0"))
except (ValueError, TypeError):
except (ValueError, TypeError) as e:
# Skip models with invalid pricing data
logger.warning(f"Skipping model with invalid pricing data: {e}")
continue
# Check if both costs are at or below threshold
if prompt_cost <= max_cost and completion_cost <= max_cost:
filtered.append(model)
# logger.info(f"[{MODULE_NAME}] Filtered {len(filtered)}/{len(models)} models at max_cost={max_cost}")
logger.info(f"[{MODULE_NAME}] Filtered {len(filtered)}/{len(models)} models at max_cost={max_cost}")
return filtered
except Exception as e:
# logger.info(f"[{MODULE_NAME}] Error filtering models: {e}")
logger.error(f"Error filtering models: {e}")
return []
@@ -264,14 +265,14 @@ def get_model_by_id(model_id: str, api_key: Optional[str] = None) -> Optional[Di
for model in all_models:
if model.get("id") == model_id:
# logger.info(f"[{MODULE_NAME}] Found model: {model_id}")
logger.info(f"[{MODULE_NAME}] Found model: {model_id}")
return model
# logger.info(f"[{MODULE_NAME}] Model not found: {model_id}")
logger.info(f"[{MODULE_NAME}] Model not found: {model_id}")
return None
except Exception as e:
# logger.info(f"[{MODULE_NAME}] Error finding model {model_id}: {e}")
logger.error(f"Error finding model {model_id}: {e}")
return None
@@ -314,7 +315,7 @@ def extract_model_metadata(model: Dict) -> Dict:
return metadata
except Exception as e:
# logger.info(f"[{MODULE_NAME}] Error extracting metadata: {e}")
logger.error(f"Error extracting metadata: {e}")
return {}
@@ -331,7 +332,7 @@ def list_model_ids(models: List[Dict]) -> List[str]:
try:
return [model.get("id", "") for model in models if model.get("id")]
except Exception as e:
# logger.info(f"[{MODULE_NAME}] Error extracting IDs: {e}")
logger.error(f"Error extracting model IDs: {e}")
return []
@@ -61,7 +61,7 @@ def read_json(file_path: Path) -> Optional[Dict[str, Any]]:
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
# logger.error(f"Failed to read {file_path}: {e}")
logger.error(f"Failed to read {file_path}: {e}")
return None
@@ -84,7 +84,7 @@ def write_json(file_path: Path, data: Dict[str, Any]) -> bool:
return True
except Exception as e:
# logger.error(f"Failed to write {file_path}: {e}")
logger.error(f"Failed to write {file_path}: {e}")
return False
@@ -166,7 +166,7 @@ def provision_json_folder(json_folder: Path) -> bool:
return True
json_folder.mkdir(parents=True, exist_ok=True)
# logger.info(f"Created JSON folder: {json_folder}")
logger.info(f"Created JSON folder: {json_folder}")
logger.info(f"Created JSON folder: {json_folder}")
return True
@@ -205,7 +205,7 @@ def create_caller_config(caller: str, json_folder: Path) -> Dict[str, Any]:
# logger.error(f"Failed to write config for {caller}")
return {}
# logger.info(f"Created API config for {caller}: {config_file}")
logger.info(f"Created API config for {caller}: {config_file}")
logger.info(f"Created config: {config_file.name}")
# Create data file
@@ -213,7 +213,7 @@ def create_caller_config(caller: str, json_folder: Path) -> Dict[str, Any]:
data = get_default_caller_data()
if write_json(data_file, data):
# logger.info(f"Created data file for {caller}: {data_file}")
logger.info(f"Created data file for {caller}: {data_file}")
logger.info(f"Created data: {data_file.name}")
# Create log file
@@ -221,7 +221,7 @@ def create_caller_config(caller: str, json_folder: Path) -> Dict[str, Any]:
log_data = get_default_caller_log()
if write_json(log_file, log_data):
# logger.info(f"Created log file for {caller}: {log_file}")
logger.info(f"Created log file for {caller}: {log_file}")
logger.info(f"Created log: {log_file.name}")
logger.info(f"Auto-provisioned OpenRouter config for '{caller}'")
@@ -256,9 +256,8 @@ def ensure_caller_config(caller: str | None = None) -> Dict[str, Any]:
detected_caller, json_folder = detect_caller_from_stack()
if detected_caller:
caller = detected_caller
# logger.info(f"Auto-detected caller: {caller}")
logger.info(f"Auto-detected caller: {caller}")
else:
# logger.warning("Could not detect caller from stack")
logger.warning("Could not detect caller module")
return {}
@@ -276,11 +275,10 @@ def ensure_caller_config(caller: str | None = None) -> Dict[str, Any]:
if config_file.exists():
config = read_json(config_file)
if config:
# logger.info(f"Using existing config for {caller}")
logger.info(f"Using existing config for {caller}")
return config
else:
# logger.warning(f"Config file corrupted for {caller}, regenerating")
logger.warning("Corrupted config, regenerating...")
logger.warning(f"Config file corrupted for {caller}, regenerating")
# Create new config
return create_caller_config(caller, json_folder)
@@ -29,6 +29,9 @@ from typing import Dict, Any, List, Optional
# Standard library for JSON operations
import json
# Logging
from aipass.prax import logger
# JSON handler
from aipass.api.apps.handlers.json import json_handler
@@ -62,14 +65,14 @@ def get_caller_usage(caller: str) -> Dict[str, Any]:
# Load usage data from JSON
data_path = API_JSON_DIR / DATA_FILE
if not data_path.exists():
# logger.info(f"[{MODULE_NAME}] No usage data file found")
logger.info(f"[{MODULE_NAME}] No usage data file found")
return {}
with open(data_path, 'r', encoding='utf-8') as f:
data = json.load(f)
if not data or "data" not in data:
# logger.info(f"[{MODULE_NAME}] No usage data available")
logger.info(f"[{MODULE_NAME}] No usage data available")
return {}
# Extract caller-specific data
@@ -77,14 +80,14 @@ def get_caller_usage(caller: str) -> Dict[str, Any]:
caller_data = usage_by_caller.get(caller, {})
if not caller_data:
# logger.info(f"[{MODULE_NAME}] No usage data found for caller: {caller}")
logger.info(f"[{MODULE_NAME}] No usage data found for caller: {caller}")
return {}
# logger.info(f"[{MODULE_NAME}] Retrieved usage stats for {caller}: {caller_data.get('requests', 0)} requests")
logger.info(f"[{MODULE_NAME}] Retrieved usage stats for {caller}: {caller_data.get('requests', 0)} requests")
return caller_data
except Exception as e:
# logger.error(f"[{MODULE_NAME}] Failed to get caller usage for {caller}: {e}")
logger.error(f"[{MODULE_NAME}] Failed to get caller usage for {caller}: {e}")
return {}
@@ -103,28 +106,28 @@ def get_session_summary(session_id: Optional[str] = None) -> Dict[str, Any]:
# Load usage data from JSON
data_path = API_JSON_DIR / DATA_FILE
if not data_path.exists():
# logger.info(f"[{MODULE_NAME}] No session data file found")
logger.info(f"[{MODULE_NAME}] No session data file found")
return {}
with open(data_path, 'r', encoding='utf-8') as f:
data = json.load(f)
if not data or "data" not in data:
# logger.info(f"[{MODULE_NAME}] No session data available")
logger.info(f"[{MODULE_NAME}] No session data available")
return {}
# Extract session summary
session_data = data["data"].get("current_session", {})
if not session_data:
# logger.info(f"[{MODULE_NAME}] No session summary found")
logger.info(f"[{MODULE_NAME}] No session summary found")
return {}
# logger.info(f"[{MODULE_NAME}] Retrieved session summary: {session_data.get('total_requests', 0)} requests")
logger.info(f"[{MODULE_NAME}] Retrieved session summary: {session_data.get('total_requests', 0)} requests")
return session_data
except Exception as e:
# logger.error(f"[{MODULE_NAME}] Failed to get session summary: {e}")
logger.error(f"[{MODULE_NAME}] Failed to get session summary: {e}")
return {}
@@ -147,14 +150,14 @@ def get_daily_usage(date: Optional[str] = None) -> Dict[str, Any]:
# Load usage data from JSON
data_path = API_JSON_DIR / DATA_FILE
if not data_path.exists():
# logger.info(f"[{MODULE_NAME}] No daily usage data file found")
logger.info(f"[{MODULE_NAME}] No daily usage data file found")
return {}
with open(data_path, 'r', encoding='utf-8') as f:
data = json.load(f)
if not data or "data" not in data:
# logger.info(f"[{MODULE_NAME}] No daily usage data available")
logger.info(f"[{MODULE_NAME}] No daily usage data available")
return {}
# Extract daily totals
@@ -162,14 +165,14 @@ def get_daily_usage(date: Optional[str] = None) -> Dict[str, Any]:
daily_data = daily_totals.get(date, {})
if not daily_data:
# logger.info(f"[{MODULE_NAME}] No usage data found for date: {date}")
logger.info(f"[{MODULE_NAME}] No usage data found for date: {date}")
return {}
# logger.info(f"[{MODULE_NAME}] Retrieved daily usage for {date}: {daily_data.get('requests', 0)} requests")
logger.info(f"[{MODULE_NAME}] Retrieved daily usage for {date}: {daily_data.get('requests', 0)} requests")
return daily_data
except Exception as e:
# logger.error(f"[{MODULE_NAME}] Failed to get daily usage for {date}: {e}")
logger.error(f"[{MODULE_NAME}] Failed to get daily usage for {date}: {e}")
return {}
@@ -185,7 +188,7 @@ def calculate_totals(usage_data: List[Dict]) -> Dict[str, float]:
"""
try:
if not usage_data:
# logger.info(f"[{MODULE_NAME}] No usage data provided for totals calculation")
logger.info(f"[{MODULE_NAME}] No usage data provided for totals calculation")
return {
"total_cost": 0.0,
"total_tokens": 0,
@@ -224,12 +227,12 @@ def calculate_totals(usage_data: List[Dict]) -> Dict[str, float]:
"total_latency": total_latency
}
# logger.info(f"[{MODULE_NAME}] Calculated totals: {result['total_requests']} requests, ${result['total_cost']:.6f}")
logger.info(f"[{MODULE_NAME}] Calculated totals: {result['total_requests']} requests, ${result['total_cost']:.6f}")
json_handler.log_operation("usage_aggregated", {"total_requests": result["total_requests"], "total_cost": result["total_cost"]})
return result
except Exception as e:
# logger.error(f"[{MODULE_NAME}] Failed to calculate totals: {e}")
logger.error(f"[{MODULE_NAME}] Failed to calculate totals: {e}")
return {
"total_cost": 0.0,
"total_tokens": 0,
@@ -253,14 +256,14 @@ def get_model_breakdown(caller: Optional[str] = None) -> Dict[str, Dict[str, int
try:
data_path = API_JSON_DIR / DATA_FILE
if not data_path.exists():
# logger.info(f"[{MODULE_NAME}] No model breakdown data file found")
logger.info(f"[{MODULE_NAME}] No model breakdown data file found")
return {}
with open(data_path, 'r', encoding='utf-8') as f:
data = json.load(f)
if not data or "data" not in data:
# logger.info(f"[{MODULE_NAME}] No model breakdown data available")
logger.info(f"[{MODULE_NAME}] No model breakdown data available")
return {}
model_stats = {}
@@ -281,9 +284,9 @@ def get_model_breakdown(caller: Optional[str] = None) -> Dict[str, Dict[str, int
model_stats[model] = {"requests": 0}
model_stats[model]["requests"] += count
# logger.info(f"[{MODULE_NAME}] Retrieved model breakdown: {len(model_stats)} models")
logger.info(f"[{MODULE_NAME}] Retrieved model breakdown: {len(model_stats)} models")
return model_stats
except Exception as e:
# logger.error(f"[{MODULE_NAME}] Failed to get model breakdown: {e}")
logger.error(f"[{MODULE_NAME}] Failed to get model breakdown: {e}")
return {}
+12 -9
View File
@@ -37,7 +37,7 @@ def _read_json(file_path: Path) -> Optional[Dict]:
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
# logger.error(f"Failed to read JSON from {file_path}: {e}")
logger.error(f"Failed to read JSON from {file_path}: {e}")
return None
@@ -49,7 +49,7 @@ def _write_json(file_path: Path, data: Dict) -> bool:
json.dump(data, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
# logger.error(f"Failed to write JSON to {file_path}: {e}")
logger.error(f"Failed to write JSON to {file_path}: {e}")
return False
@@ -91,7 +91,7 @@ def cleanup_old_data(data_file_path: Path, retention_days: int = 30) -> int:
data["timestamp"] = datetime.now().isoformat()
_write_json(data_file_path, data)
# logger.info(f"Cleaned up {len(old_generations)} generation entries")
logger.info(f"Cleaned up {len(old_generations)} generation entries")
logger.info(f"Cleaned up {len(old_generations)} generation entries older than {retention_days} days")
json_handler.log_operation("usage_cleanup", {"generations_removed": len(old_generations), "retention_days": retention_days})
@@ -118,7 +118,8 @@ def _identify_old_generations(generation_tracking: Dict, cutoff_date: datetime)
if gen_date < cutoff_date:
old_generations.append(gen_id)
except (ValueError, TypeError):
except (ValueError, TypeError) as e:
logger.warning(f"Invalid timestamp for generation {gen_id}, marking for cleanup: {e}")
old_generations.append(gen_id)
return old_generations
@@ -141,7 +142,8 @@ def cleanup_daily_totals(data_file_path: Path, retention_days: int = 90) -> int:
date_obj = datetime.fromisoformat(date_str).date()
if date_obj < cutoff_date:
old_dates.append(date_str)
except (ValueError, TypeError):
except (ValueError, TypeError) as e:
logger.warning(f"Invalid date format '{date_str}', marking for cleanup: {e}")
old_dates.append(date_str)
if not old_dates:
@@ -155,7 +157,7 @@ def cleanup_daily_totals(data_file_path: Path, retention_days: int = 90) -> int:
data["timestamp"] = datetime.now().isoformat()
_write_json(data_file_path, data)
# logger.info(f"Cleaned up {len(old_dates)} daily total entries")
logger.info(f"Cleaned up {len(old_dates)} daily total entries")
logger.info(f"Cleaned up {len(old_dates)} daily total entries older than {retention_days} days")
return len(old_dates)
@@ -175,7 +177,7 @@ def auto_cleanup(data_file_path: Path, config: Optional[Dict] = None) -> Dict[st
generations_removed = cleanup_old_data(data_file_path, gen_retention)
daily_totals_removed = cleanup_daily_totals(data_file_path, daily_retention)
# logger.info(f"Auto cleanup: {generations_removed} generations, {daily_totals_removed} daily totals removed")
logger.info(f"Auto cleanup: {generations_removed} generations, {daily_totals_removed} daily totals removed")
return {
"generations_removed": generations_removed,
@@ -225,7 +227,7 @@ def get_cleanup_stats(data_file_path: Path) -> Dict[str, int]:
}
except Exception as e:
# logger.error(f"Failed to get cleanup stats: {e}")
logger.error(f"Failed to get cleanup stats: {e}")
return empty_stats
@@ -234,5 +236,6 @@ def _is_old_date(date_str: str, cutoff_date) -> bool:
try:
date_obj = datetime.fromisoformat(date_str).date()
return date_obj < cutoff_date
except (ValueError, TypeError):
except (ValueError, TypeError) as e:
logger.warning(f"Invalid date format '{date_str}' in _is_old_date: {e}")
return True
+12 -9
View File
@@ -29,6 +29,9 @@ import time
from datetime import datetime
from typing import Dict, Any, Optional
# Logging
from aipass.prax import logger
# JSON handler
from aipass.api.apps.handlers.json import json_handler
@@ -82,7 +85,7 @@ def track_usage(generation_id: str, caller: str, model: str = "unknown", api_key
from aipass.api.apps.handlers.auth.keys import get_api_key
api_key = get_api_key("openrouter")
except Exception as e:
# Failed to load API key
logger.error(f"[{MODULE_NAME}] Failed to load API key: {e}")
return {"success": False, "error": "No API key available"}
if not api_key:
@@ -109,7 +112,7 @@ def track_usage(generation_id: str, caller: str, model: str = "unknown", api_key
return {"success": False, "error": "Failed to store usage data"}
except Exception as e:
# Usage tracking failed
logger.error(f"[{MODULE_NAME}] Usage tracking failed: {e}")
return {"success": False, "error": str(e)}
@@ -176,20 +179,20 @@ def get_generation_metrics(generation_id: str, api_key: str) -> Optional[Dict[st
# OpenRouter API returned non-200 status
return None
except requests.exceptions.Timeout:
# Request timeout querying generation
except requests.exceptions.Timeout as e:
logger.warning(f"[{MODULE_NAME}] Request timeout querying generation: {e}")
return None
except requests.exceptions.RequestException as e:
# Request error querying generation
logger.error(f"[{MODULE_NAME}] Request error querying generation: {e}")
return None
except (ValueError, KeyError) as e:
# Error parsing metrics
logger.error(f"[{MODULE_NAME}] Error parsing metrics: {e}")
return None
except Exception as e:
# Unexpected error getting generation metrics
logger.error(f"[{MODULE_NAME}] Unexpected error getting generation metrics: {e}")
return None
@@ -304,7 +307,7 @@ def store_usage_data(caller: str, model: str, generation_id: str, metrics: Dict[
return True
except Exception as e:
# Failed to store usage data
logger.error(f"[{MODULE_NAME}] Failed to store usage data: {e}")
return False
@@ -330,5 +333,5 @@ def load_usage_data() -> Dict[str, Any]:
return data_wrapper.get("data", {})
except Exception as e:
# Failed to load usage data
logger.error(f"[{MODULE_NAME}] Failed to load usage data: {e}")
return {}
+1 -1
View File
@@ -226,6 +226,6 @@ if __name__ == "__main__":
console.print()
console.print(f"[red]Unknown command: {command}[/red]")
console.print()
console.print("Run [dim]python3 api_key.py --help[/dim] for available commands")
console.print("Run [dim]drone @api --help[/dim] for available commands")
console.print()
sys.exit(1)
+1 -3
View File
@@ -350,8 +350,6 @@ if __name__ == "__main__":
console.print()
console.print(f"[red]Unknown command: {command}[/red]")
console.print()
console.print(
"Run [dim]python3 google_client.py --help[/dim] for available commands"
)
console.print("Run [dim]drone @api --help[/dim] for available commands")
console.print()
sys.exit(1)
@@ -281,6 +281,7 @@ def check_status():
import openai # noqa: F401
console.print(f" [cyan]OpenAI SDK:[/cyan] [green]available[/green]")
except ImportError:
logger.warning("OpenAI SDK not installed")
console.print(f" [cyan]OpenAI SDK:[/cyan] [red]missing[/red]")
# Client cache stats
@@ -343,6 +344,6 @@ if __name__ == "__main__":
console.print()
console.print(f"[red]Unknown command: {command}[/red]")
console.print()
console.print("Run [dim]python3 openrouter_client.py --help[/dim] for available commands")
console.print("Run [dim]drone @api --help[/dim] for available commands")
console.print()
sys.exit(1)
+2 -2
View File
@@ -251,7 +251,7 @@ def cleanup_data(args: List[str]):
from aipass.trigger.apps.modules.core import trigger
trigger.fire('usage_data_cleaned', days=days, data_path=str(data_path))
except ImportError:
pass # Silent fallback
logger.warning("Trigger module not available — skipping event fire")
else:
error("Cleanup failed")
@@ -280,6 +280,6 @@ if __name__ == "__main__":
console.print()
console.print(f"[red]Unknown command: {command}[/red]")
console.print()
console.print("Run [dim]python3 usage_tracker.py --help[/dim] for available commands")
console.print("Run [dim]drone @api --help[/dim] for available commands")
console.print()
sys.exit(1)