"""Per-conversation routing hints shared by agent and auxiliary Responses calls.""" from collections.abc import Mapping import hashlib import re from typing import Any from uuid import UUID def cache_affinity_options( ctx: Any, role: str | None, model_name: str | None, options: dict | None = None ) -> dict: """Keep body and header routing keys aligned without changing model defaults. An exploration survives multiple execution sessions. Each model role keeps its own cache scope so background summaries do not compete with the agent. Set hints per call: raw models/HTTP clients may be shared by concurrent tasks. Explicit caller keys and headers take precedence (including underscore aliases). """ result = dict(options or {}) engine = getattr(ctx, "data_engine", None) session = getattr(ctx, "exploration_id", None) or getattr(engine, "current_session_id", None) if isinstance(session, (str, UUID)) and str(session): identity = f"{session}:{role or 'model'}:{model_name}" result.setdefault( "prompt_cache_key", "artemis:" + hashlib.sha256(identity.encode()).hexdigest()[:40] ) key = result.get("prompt_cache_key") if not isinstance(key, str) or not re.fullmatch(r"[-0-9A-Za-z._:]{1,128}", key): return result existing = result.get("extra_headers") if existing is not None and not isinstance(existing, Mapping): return result headers = dict(existing or {}) names = {name.lower().replace("_", "-") for name in headers} for name in ("Thread-Id", "Session-Id"): if name.lower() not in names: headers[name] = key result["extra_headers"] = headers return result