feat(api): seedgo compliance — json_handler, introspection, diagnostics (#69)

* fix(ai_mail): resolve relative mailbox_path bug + archive dead handlers

Fix get_user_by_email() and get_all_users() returning relative mailbox_path
values from registry instead of absolute paths (causing doubled paths in reply).
Added path resolution matching get_current_user() pattern.

Archive 7 unused handler files to apps/.archive/ (gitignored):
- pending_work.py — planned feature, never wired
- lock_utils.py — superseded by inbox_lock.py
- data_ops.py — consumer (error_monitor) was archived
- config_generator.py, users/load.py, registry/validate.py — scaffolded, never used
- trigger/error_handler.py — superseded by trigger branch's event system

Added test_user_paths.py (13 tests) covering absolute path resolution.

Co-Authored-By: @ai_mail <ai_mail@aipass>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(spawn): seedgo compliance cleanup — 93% to 98%

Archive legacy files (dev.local.md, ai_mail.local/, .seed/). Update META
Modified dates on 22 files. Update README architecture tree and freshness.
Fix 15 Pyright unused warnings. Add introspection no-args gates to 6
modules. Wire json_handler + log_operation to 15 operational files. Add
seedgo bypass for 5 pure data files and log_structure. Remove deprecated
dev.local.md from builder template.

Co-Authored-By: @spawn <spawn@aipass>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(api): seedgo compliance — json_handler, introspection, diagnostics

Wire json_handler.log_operation() into all 13 handler files.
Add no-args introspection gates to 4 modules with standalone
command routing. Add pyright directives for optional deps
(Google, OpenAI) on 3 handler files.

Co-Authored-By: @api <api@aipass>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: @ai_mail <ai_mail@aipass>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: @spawn <spawn@aipass>
Co-authored-by: @api <api@aipass>
This commit is contained in:
AIPass
2026-03-17 16:44:57 -07:00
committed by GitHub
co-authored by @api Claude Opus 4.6 @ai_mail @spawn
parent 8c3ad2657c
commit 18cc8eb74f
16 changed files with 146 additions and 25 deletions
+4
View File
@@ -29,6 +29,9 @@ from typing import Optional, Dict, List
# Logging
from aipass.prax import logger
# JSON handler
from aipass.api.apps.handlers.json import json_handler
# ==============================================
# CONSTANTS
@@ -86,6 +89,7 @@ def read_env_file(env_var: str, search_paths: Optional[List[Path]] = None) -> Op
key, value = line.split('=', 1)
if key.strip() == env_var:
# Found env_var in env_file
json_handler.log_operation("env_loaded", {"variable": env_var, "source": str(env_file)})
return value.strip()
except Exception as e:
# Error reading env_file
+20 -9
View File
@@ -31,6 +31,9 @@ from typing import Optional, Dict, Any
# Internal handlers
from aipass.api.apps.handlers.auth.env import read_env_file
# JSON handler
from aipass.api.apps.handlers.json import json_handler
# ==============================================
# CONSTANTS
@@ -86,23 +89,31 @@ def get_api_key(provider: str = "openrouter") -> Optional[str]:
... print(f"Got key: {key[:20]}...")
"""
try:
source = ""
# 1. Try config file
key = get_key_from_config(provider)
if key and validate_key(key, provider):
# Using key from config
return key
source = "config"
# 2. Try environment variable
key = get_key_from_env(provider)
if key and validate_key(key, provider):
# Using key from environment
return key
if not source:
key = get_key_from_env(provider)
if key and validate_key(key, provider):
# Using key from environment
source = "env"
# 3. Try .env file
env_var = f"{provider.upper()}_API_KEY"
key = read_env_file(env_var)
if key and validate_key(key, provider):
# Using key from .env file
if not source:
env_var = f"{provider.upper()}_API_KEY"
key = read_env_file(env_var)
if key and validate_key(key, provider):
# Using key from .env file
source = "dotenv"
if source:
json_handler.log_operation("key_retrieved", {"provider": provider, "source": source})
return key
# No valid key found
@@ -31,6 +31,9 @@ from typing import Dict, Any, Optional
# Internal handlers
from aipass.api.apps.handlers.json.json_handler import load_json, save_json
# JSON handler
from aipass.api.apps.handlers.json import json_handler
# Logging
from aipass.prax import logger
@@ -115,6 +118,7 @@ def load_provider_config(provider: str = "openrouter") -> Optional[Dict[str, Any
if provider_config:
# Loaded config for provider
json_handler.log_operation("config_loaded", {"provider": provider})
return provider_config
else:
# Provider not found in config
@@ -5,6 +5,7 @@
# Created: 2026-03-14
# Modified: 2026-03-14
# =============================================
# pyright: reportMissingImports=false, reportInvalidTypeForm=false, reportOptionalMemberAccess=false, reportOptionalCall=false
"""
Google OAuth2 Authentication Handler
@@ -22,6 +23,9 @@ Consumers get authenticated credentials, they decide what to do with them.
from pathlib import Path
from typing import Optional
# JSON handler
from aipass.api.apps.handlers.json import json_handler
# =============================================
# CONSTANTS
# =============================================
@@ -84,6 +88,7 @@ def load_credentials(scopes: Optional[list] = None) -> Optional["Credentials"]:
try:
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:
return None
@@ -29,6 +29,9 @@ import ssl
import time
from typing import Any, Callable, Optional
# JSON handler
from aipass.api.apps.handlers.json import json_handler
def is_ssl_error(exc: Exception) -> bool:
"""Check if an exception is a transient SSL/connection error.
@@ -81,6 +84,7 @@ def api_call_with_retry(
except Exception as e:
if attempt < max_retries and is_ssl_error(e):
wait = 2 ** attempt
json_handler.log_operation("api_retry_attempted", {"attempt": attempt + 1, "wait_seconds": wait})
time.sleep(wait)
if rebuild_service_fn:
rebuild_service_fn()
@@ -27,6 +27,9 @@ from pathlib import Path
import inspect
from typing import Dict, Any, Optional, Tuple
# JSON handler
from aipass.api.apps.handlers.json import json_handler
# =============================================
# CONFIGURATION
# =============================================
@@ -61,11 +64,17 @@ def get_caller_info() -> Optional[Dict[str, Any]]:
frame_path = Path(frame_info.filename)
if "flow" in frame_path.parts:
return _detect_flow_caller(frame_path)
result = _detect_flow_caller(frame_path)
json_handler.log_operation("caller_detected", {"caller": result.get("caller_name"), "category": "flow"})
return result
elif "prax" in frame_path.parts:
return _detect_prax_caller(frame_path)
result = _detect_prax_caller(frame_path)
json_handler.log_operation("caller_detected", {"caller": result.get("caller_name"), "category": "prax"})
return result
elif any("skills" in part for part in frame_path.parts):
return _detect_skills_caller(frame_path)
result = _detect_skills_caller(frame_path)
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")
return None
@@ -5,6 +5,7 @@
# Created: 2025-11-15
# Modified: 2025-11-15
# =============================================
# pyright: reportInvalidTypeForm=false, reportOptionalCall=false
"""
OpenRouter Client Handler
@@ -49,6 +50,7 @@ try:
OPENAI_AVAILABLE = True
except ImportError:
# logger.error("OpenAI SDK not available. Install with: pip install openai")
OpenAI = None # type: ignore[assignment,misc]
OPENAI_AVAILABLE = False
# Handler imports
@@ -56,6 +58,9 @@ from aipass.api.apps.handlers.auth.keys import get_api_key
from aipass.api.apps.handlers.openrouter.caller import get_caller_info
from aipass.api.apps.handlers.usage.tracking import track_usage
# JSON handler
from aipass.api.apps.handlers.json import json_handler
# =============================================
# CONFIGURATION
# =============================================
@@ -116,6 +121,7 @@ def create_client(api_key: str, base_url: str = OPENROUTER_BASE_URL, timeout: in
)
# 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
except Exception as e:
@@ -34,6 +34,9 @@ import requests
# Internal imports
from aipass.api.apps.handlers.auth.keys import get_api_key
# JSON handler
from aipass.api.apps.handlers.json import json_handler
# =============================================
# CONSTANTS
@@ -80,6 +83,7 @@ def get_available_models(api_key: Optional[str] = None) -> List[Dict]:
if models:
# 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")
@@ -36,6 +36,9 @@ from aipass.prax import logger
from aipass.api.apps.handlers.openrouter.caller import detect_caller_from_stack
# JSON handler
from aipass.api.apps.handlers.json import json_handler
# ===========================================
# JSON UTILITIES
@@ -223,6 +226,7 @@ def create_caller_config(caller: str, json_folder: Path) -> Dict[str, Any]:
logger.info(f"Auto-provisioned OpenRouter config for '{caller}'")
logger.warning("Reload config and retry request")
json_handler.log_operation("provider_provisioned", {"caller": caller})
return config
@@ -29,6 +29,9 @@ from typing import Dict, Any, List, Optional
# Standard library for JSON operations
import json
# JSON handler
from aipass.api.apps.handlers.json import json_handler
# =============================================
# MODULE CONSTANTS
@@ -222,6 +225,7 @@ def calculate_totals(usage_data: List[Dict]) -> Dict[str, float]:
}
# 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:
@@ -25,6 +25,9 @@ from typing import Optional, Dict, List
# Logging
from aipass.prax import logger
# JSON handler
from aipass.api.apps.handlers.json import json_handler
def _read_json(file_path: Path) -> Optional[Dict]:
"""Read JSON file with error handling."""
@@ -90,6 +93,7 @@ def cleanup_old_data(data_file_path: Path, retention_days: int = 30) -> int:
_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 older than {retention_days} days")
json_handler.log_operation("usage_cleanup", {"generations_removed": len(old_generations), "retention_days": retention_days})
return len(old_generations)
@@ -29,6 +29,9 @@ import time
from datetime import datetime
from typing import Dict, Any, Optional
# JSON handler
from aipass.api.apps.handlers.json import json_handler
# =============================================
# MODULE CONSTANTS
# =============================================
@@ -99,6 +102,7 @@ def track_usage(generation_id: str, caller: str, model: str = "unknown", api_key
# Store the usage data
if store_usage_data(caller, model, generation_id, metrics):
# Successfully tracked usage
json_handler.log_operation("usage_tracked", {"caller": caller, "model": model, "generation_id": generation_id})
return {"success": True, "metrics": metrics}
else:
# Failed to store usage data
+19 -4
View File
@@ -65,17 +65,32 @@ def handle_command(command: str, args: List[str]) -> bool:
if command not in ["get-key", "validate", "list-providers", "init"]:
return False
# Help gate
if args and args[0] in ("--help", "-h", "help"):
print_help()
return True
# Log operation
json_handler.log_operation(f"api_key_{command}", {"command": command})
# Standalone commands — route before introspection gate
if command == "list-providers":
list_providers()
return True
if command == "init":
init_env()
return True
# NO-ARGS GATE (seedgo standard)
if not args:
print_introspection()
return True
# Arg-required commands
if command == "get-key":
get_key(args)
elif command == "validate":
validate_key(args)
elif command == "list-providers":
list_providers()
elif command == "init":
init_env()
return True
except Exception as e:
+13 -1
View File
@@ -124,8 +124,20 @@ def handle_command(command: str, args: List[str]) -> bool:
Returns:
True if command was handled, False to pass through.
"""
# Help gate
if args and args[0] in ("--help", "-h", "help"):
print_help()
return True
# NO-ARGS GATE (seedgo standard)
if not args:
if command == "google":
print_introspection()
return True
return False
# Only handle commands with "google" as the provider argument
if not args or args[0] != "google":
if args[0] != "google":
return False
if command == "validate":
@@ -125,17 +125,33 @@ def handle_command(command: str, args: List[str]) -> bool:
if command not in ["test", "call", "models", "status"]:
return False
# Help gate
if args and args[0] in ("--help", "-h", "help"):
print_help()
return True
# Log operation
json_handler.log_operation(f"openrouter_{command}", {"command": command})
# Standalone commands — route before introspection gate
if command == "test":
test_connection()
elif command == "call":
make_call(args)
elif command == "models":
return True
if command == "models":
list_models()
elif command == "status":
return True
if command == "status":
check_status()
return True
# NO-ARGS GATE (seedgo standard)
if not args:
print_introspection()
return True
# Arg-required commands
if command == "call":
make_call(args)
return True
except Exception as e:
+19 -4
View File
@@ -134,15 +134,30 @@ def handle_command(command: str, args: List[str]) -> bool:
if command not in ["track", "stats", "session", "caller-usage", "cleanup"]:
return False
# Help gate
if args and args[0] in ("--help", "-h", "help"):
print_help()
return True
# Log operation
json_handler.log_operation(f"usage_{command}", {"command": command})
# Standalone commands — route before introspection gate
if command == "stats":
show_stats()
return True
if command == "session":
show_session()
return True
# NO-ARGS GATE (seedgo standard)
if not args:
print_introspection()
return True
# Arg-required commands
if command == "track":
track_usage(args)
elif command == "stats":
show_stats()
elif command == "session":
show_session()
elif command == "caller-usage":
show_caller_usage(args)
elif command == "cleanup":