from __future__ import annotations from .shared_part_01 import * def _normalize_provider(raw: Optional[str]) -> str: provider = str(raw or "").strip().lower() provider = PROVIDER_ALIASES.get(provider, provider) if provider not in SUPPORTED_PROVIDERS: raise ValueError("provider 仅支持 deepseek/glm/openai/ollama") return provider def _mask_secret(secret: str) -> str: value = str(secret or "").strip() if not value: return "" if len(value) <= 8: return "*" * len(value) return f"{value[:4]}...{value[-4:]}" def _safe_json(value: Any) -> dict[str, Any]: if isinstance(value, dict): try: # 返回深拷贝,避免原地修改导致 SQLAlchemy 不感知 JSON 字段变更 return json.loads(json.dumps(value)) except Exception: return dict(value) if isinstance(value, str): try: parsed = json.loads(value) return parsed if isinstance(parsed, dict) else {} except Exception: return {} return {} def _env_bool(name: str, default: bool = False) -> bool: raw = os.getenv(name) if raw is None: return default return str(raw).strip().lower() in BOOL_TRUE_VALUES def _parse_csv_items(raw: str) -> tuple[str, ...]: rows = [item.strip().lower() for item in str(raw or "").split(",") if item.strip()] return tuple(rows) def _cn_now_text() -> str: cn_tz = timezone(timedelta(hours=8)) return datetime.now(cn_tz).strftime("%Y-%m-%d %H:%M:%S") def _cn_now_text_ms() -> str: cn_tz = timezone(timedelta(hours=8)) return datetime.now(cn_tz).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] def _runtime_log(scope: str, message: str) -> None: print(f"[{_cn_now_text()}][{scope}] {message}") def _looks_like_openclaw_timeout_message(text: str) -> bool: lowered = str(text or "").strip().lower() if not lowered: return False timeout_signals = ( "request timed out before a response was generated", "agents.defaults.timeoutseconds", "timeoutseconds", "before a response was generated", ) return any(signal in lowered for signal in timeout_signals) @dataclass class ChatCompletionResult: text: str raw_response: Any = None @dataclass class StructuredExtractionResult: payload: Optional[dict[str, Any]] source_field: str provider: str model: str error_code: Optional[str] = None raw_text: str = "" validation: str = "invalid" from_cache: bool = False __all__ = [name for name in globals() if not name.startswith("__")]