Skip to content

resolver

resolver

Turning « sur mon PC » into a device id — or refusing to guess.

Spec §22. The assistant hears a phrase, not an identifier. This module is the only place allowed to bridge the two, and its governing rule is that a wrong guess is worse than a question: opening a screen on the wrong machine is confusing, and sending a notification to the wrong one is a small betrayal of trust. So when two devices match equally well, this returns an AMBIGUOUS answer with the candidates rather than picking the first.

The one place it does decide on its own: a single obvious match, or a tie broken by reachability — because "the phone that is on" is what a person means by "my phone" when only one is awake.

Functions:

resolve_device

resolve_device(
    phrase: str,
    devices: Sequence[Mapping[str, Any]],
    *,
    local_device_id: str = "",
) -> dict[str, Any]

Read phrase against the fleet and say who is meant — or that it is unclear.

Returns one of four outcomes, each with a French sentence the assistant can say verbatim:

LOCAL the user means this machine; do not route anything. RESOLVED one device, with device. AMBIGUOUS several plausible, with candidates — ask, do not pick. UNKNOWN nothing matched, with candidates listing what exists.

Source code in src/diapason/mesh/resolver.py
def resolve_device(
    phrase: str,
    devices: Sequence[Mapping[str, Any]],
    *,
    local_device_id: str = "",
) -> dict[str, Any]:
    """Read *phrase* against the fleet and say who is meant — or that it is unclear.

    Returns one of four outcomes, each with a French sentence the assistant
    can say verbatim:

    ``LOCAL``      the user means this machine; do not route anything.
    ``RESOLVED``   one device, with ``device``.
    ``AMBIGUOUS``  several plausible, with ``candidates`` — ask, do not pick.
    ``UNKNOWN``    nothing matched, with ``candidates`` listing what exists.
    """
    folded = _fold(phrase)
    fleet = [
        d
        for d in devices
        if d.get("trustLevel") == "TRUSTED" and d.get("deviceId") != local_device_id
    ]

    if folded and any(w in folded for w in _HERE_WORDS):
        return {
            "status": "LOCAL",
            "device": None,
            "candidates": [],
            "message": "C'est cet appareil-ci : rien à envoyer ailleurs.",
        }

    if not fleet:
        return {
            "status": "UNKNOWN",
            "device": None,
            "candidates": [],
            "message": (
                "Aucun autre appareil n'est appairé. Ajoutez-en un depuis "
                "l'écran Appareils pour pouvoir lui envoyer quelque chose."
            ),
        }

    # « l'autre appareil » is only meaningful when there is exactly one.
    if folded and any(w in folded for w in _OTHER_WORDS) and len(fleet) == 1:
        return _resolved(fleet[0])

    scored: list[tuple[int, Mapping[str, Any]]] = []
    for device in fleet:
        score = max(_name_score(folded, device), _kind_score(folded, device))
        if score:
            scored.append((score, device))

    if not scored:
        # No phrase at all, and only one device: that is not a guess.
        if not folded.strip() and len(fleet) == 1:
            return _resolved(fleet[0])
        return {
            "status": "UNKNOWN",
            "device": None,
            "candidates": describe_devices(fleet),
            "message": (
                "Je ne vois pas de quel appareil il s'agit. " + _list_sentence(fleet)
            ),
        }

    scored.sort(key=lambda pair: pair[0], reverse=True)
    best = scored[0][0]
    top = [device for score, device in scored if score == best]

    if len(top) > 1:
        # Being awake breaks a tie: « mon téléphone » means the one that is on.
        awake = [d for d in top if is_reachable(d)]
        if len(awake) == 1:
            return _resolved(awake[0])
        return {
            "status": "AMBIGUOUS",
            "device": None,
            "candidates": describe_devices(top),
            "message": ("Plusieurs appareils correspondent. " + _list_sentence(top)),
        }

    return _resolved(top[0])

describe_devices

describe_devices(
    devices: Sequence[Mapping[str, Any]],
) -> list[dict[str, Any]]

The fleet as the assistant should see it: names, kinds, and whether they are awake. No keys, no addresses — nothing the model needs.

Source code in src/diapason/mesh/resolver.py
def describe_devices(devices: Sequence[Mapping[str, Any]]) -> list[dict[str, Any]]:
    """The fleet as the assistant should see it: names, kinds, and whether
    they are awake. No keys, no addresses — nothing the model needs."""
    return [
        {
            "deviceId": d.get("deviceId"),
            "name": d.get("name"),
            "deviceType": d.get("deviceType"),
            "platform": d.get("platform"),
            "presence": presence_of(d)["state"],
            "reachable": is_reachable(d),
            "capabilities": list(d.get("capabilities") or []),
        }
        for d in devices
    ]