| 1 | """Session source metadata for the Director workflow.""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from typing import Any |
| 6 | |
| 7 | SESSION_SOURCE_KEY = "source" |
| 8 | SOURCE_STEPWISE = "stepwise" |
| 9 | |
| 10 | VALID_SOURCES = frozenset({SOURCE_STEPWISE}) |
| 11 | DIRECTOR_SKILL_NAME = "director" |
| 12 | |
| 13 | |
| 14 | def normalize_source(value: Any) -> str | None: |
| 15 | """Normalize persisted or resolved source strings.""" |
| 16 | if not isinstance(value, str): |
| 17 | return None |
| 18 | raw = value.strip().lower() |
| 19 | if raw in VALID_SOURCES: |
| 20 | return raw |
| 21 | return None |
| 22 | |
| 23 | def resolve_source_from_wire(data: dict[str, Any] | None) -> str | None: |
| 24 | """Resolve session source from WebSocket envelope or inbound message metadata.""" |
| 25 | if not isinstance(data, dict): |
| 26 | return None |
| 27 | return normalize_source(data.get("source")) |
| 28 | |
| 29 | def get_source(metadata: dict[str, Any] | None) -> str | None: |
| 30 | if not isinstance(metadata, dict): |
| 31 | return None |
| 32 | return normalize_source(metadata.get(SESSION_SOURCE_KEY)) |
| 33 | |
| 34 | def apply_source(metadata: dict[str, Any], source: str | None) -> bool: |
| 35 | """Set source on metadata if not already set. Returns True when updated.""" |
| 36 | normalized = normalize_source(source) |
| 37 | if not normalized: |
| 38 | return False |
| 39 | if get_source(metadata) is not None: |
| 40 | return False |
| 41 | metadata[SESSION_SOURCE_KEY] = normalized |
| 42 | return True |
| 43 |