2525from .relay_transport import BridgeRelayTransport
2626
2727
28+ def _normalize_match (value : str ) -> str :
29+ return "" .join (ch .lower () for ch in value if ch .isalnum ())
30+
31+
32+ def _resolve_named_match (
33+ items : list [Any ],
34+ id_or_name : str ,
35+ * ,
36+ get_id : Callable [[Any ], str ],
37+ get_name : Callable [[Any ], str ],
38+ ) -> Any | None :
39+ raw = id_or_name .strip ()
40+ if not raw :
41+ return None
42+
43+ lower = raw .lower ()
44+ normalized = _normalize_match (raw )
45+
46+ exact_id = [item for item in items if get_id (item ) == raw ]
47+ if len (exact_id ) == 1 :
48+ return exact_id [0 ]
49+
50+ exact_text = [
51+ item
52+ for item in items
53+ if get_id (item ).lower () == lower or get_name (item ).lower () == lower
54+ ]
55+ if len (exact_text ) == 1 :
56+ return exact_text [0 ]
57+
58+ exact_normalized = [
59+ item
60+ for item in items
61+ if _normalize_match (get_id (item )) == normalized
62+ or _normalize_match (get_name (item )) == normalized
63+ ]
64+ if len (exact_normalized ) == 1 :
65+ return exact_normalized [0 ]
66+
67+ partial = [
68+ item
69+ for item in items
70+ if lower in get_id (item ).lower ()
71+ or lower in get_name (item ).lower ()
72+ or (len (normalized ) >= 2 and normalized in _normalize_match (get_id (item )))
73+ or (len (normalized ) >= 2 and normalized in _normalize_match (get_name (item )))
74+ ]
75+ if len (partial ) == 1 :
76+ return partial [0 ]
77+
78+ return None
79+
80+
2881class DiscoveryService :
2982 """Discover, connect, and manage local and browser-backed SLOP providers."""
3083
@@ -129,7 +182,7 @@ def get_provider(self, provider_id: str) -> ConnectedProvider | None:
129182 provider = self ._providers .get (provider_id )
130183 if provider is None or provider .status != "connected" :
131184 return None
132- self ._last_accessed [ provider .id ] = asyncio . get_running_loop (). time ( )
185+ self ._touch_provider ( provider .id )
133186 return provider
134187
135188 async def ensure_connected (self , id_or_name : str ) -> ConnectedProvider | None :
@@ -426,35 +479,32 @@ def _create_transport(self, descriptor: ProviderDescriptor) -> Any | None:
426479 return None
427480
428481 def _find_connected_provider (self , id_or_name : str ) -> ConnectedProvider | None :
429- provider = self ._providers .get (id_or_name )
482+ provider = _resolve_named_match (
483+ list (self ._providers .values ()),
484+ id_or_name ,
485+ get_id = lambda item : item .id ,
486+ get_name = lambda item : item .name ,
487+ )
430488 if provider is not None and provider .status == "connected" :
431- self ._last_accessed [ provider .id ] = asyncio . get_running_loop (). time ( )
489+ self ._touch_provider ( provider .id )
432490 return provider
433-
434- needle = id_or_name .lower ()
435- for provider in self ._providers .values ():
436- if provider .status == "connected" and needle in provider .name .lower ():
437- self ._last_accessed [provider .id ] = asyncio .get_running_loop ().time ()
438- return provider
439491 return None
440492
441493 def _find_any_provider (self , id_or_name : str ) -> ConnectedProvider | None :
442- provider = self ._providers .get (id_or_name )
443- if provider is not None :
444- return provider
445-
446- needle = id_or_name .lower ()
447- for provider in self ._providers .values ():
448- if needle in provider .name .lower ():
449- return provider
450- return None
494+ return _resolve_named_match (
495+ list (self ._providers .values ()),
496+ id_or_name ,
497+ get_id = lambda item : item .id ,
498+ get_name = lambda item : item .name ,
499+ )
451500
452501 def _find_descriptor (self , id_or_name : str ) -> ProviderDescriptor | None :
453- needle = id_or_name .lower ()
454- for descriptor in self .get_discovered ():
455- if descriptor .id == id_or_name or needle in descriptor .name .lower ():
456- return descriptor
457- return None
502+ return _resolve_named_match (
503+ self .get_discovered (),
504+ id_or_name ,
505+ get_id = lambda item : item .id ,
506+ get_name = lambda item : item .name ,
507+ )
458508
459509 def _forget_provider (self , provider_id : str ) -> None :
460510 self ._providers .pop (provider_id , None )
@@ -464,6 +514,10 @@ def _forget_provider(self, provider_id: str) -> None:
464514 if reconnect_task is not None :
465515 reconnect_task .cancel ()
466516
517+ def _touch_provider (self , provider_id : str ) -> None :
518+ with contextlib .suppress (RuntimeError ):
519+ self ._last_accessed [provider_id ] = asyncio .get_running_loop ().time ()
520+
467521 def _fire_state_change (self ) -> None :
468522 for handler in self ._state_change_handlers :
469523 handler ()
0 commit comments