from __future__ import annotations from dataclasses import dataclass from .commands import PORTED_COMMANDS from .tools import PORTED_TOOLS from .models import PortingModule @dataclass(frozen=True) class RoutedMatch: kind: str name: str source_hint: str score: int class PortRuntime: def route_prompt(self, prompt: str, limit: int = 5) -> list[RoutedMatch]: tokens = {token.lower() for token in prompt.replace('/', ' ').replace('-', ' ').split() if token} by_kind = { 'command': self._collect_matches(tokens, PORTED_COMMANDS, 'command'), 'tool': self._collect_matches(tokens, PORTED_TOOLS, 'tool'), } selected: list[RoutedMatch] = [] # Prefer at least one representative from each kind when available. for kind in ('command', 'tool'): if by_kind[kind]: selected.append(by_kind[kind].pop(0)) leftovers = sorted( [match for matches in by_kind.values() for match in matches], key=lambda item: (-item.score, item.kind, item.name), ) selected.extend(leftovers[: max(0, limit - len(selected))]) return selected[:limit] def _collect_matches(self, tokens: set[str], modules: tuple[PortingModule, ...], kind: str) -> list[RoutedMatch]: matches: list[RoutedMatch] = [] for module in modules: score = self._score(tokens, module) if score > 0: matches.append(RoutedMatch(kind=kind, name=module.name, source_hint=module.source_hint, score=score)) matches.sort(key=lambda item: (-item.score, item.name)) return matches @staticmethod def _score(tokens: set[str], module: PortingModule) -> int: haystacks = [module.name.lower(), module.source_hint.lower(), module.responsibility.lower()] score = 0 for token in tokens: if any(token in haystack for haystack in haystacks): score += 1 return score