feat: Enhance API and Outbound Tracking Functionality
- Add test for fallback to bot account in platform IO route metadata when context message is absent. - Improve PlatformIOManager to avoid duplicate driver entries and streamline fallback driver handling. - Refactor OutboundTracker to support tracking by both internal message ID and driver ID, enhancing the uniqueness of pending records. - Introduce dynamic API capabilities in RuntimeComponent, allowing plugins to replace their dynamic API lists. - Update APIRegistry to manage dynamic APIs more effectively, including registration and toggling of API statuses. - Implement authorization checks for dynamic API capabilities to ensure proper permissions. - Restrict direct calls to certain host RPC methods from plugins for enhanced security. - Refactor send_service to ensure fallback to current platform account when no context message is available.
This commit is contained in:
@@ -394,23 +394,22 @@ class PlatformIOManager:
|
||||
"""
|
||||
|
||||
drivers: List[PlatformIODriver] = []
|
||||
seen_driver_ids: set[str] = set()
|
||||
for binding in self._send_route_table.resolve_bindings(route_key):
|
||||
driver = self._driver_registry.get(binding.driver_id)
|
||||
if driver is not None:
|
||||
if driver is not None and driver.driver_id not in seen_driver_ids:
|
||||
drivers.append(driver)
|
||||
if drivers:
|
||||
return drivers
|
||||
seen_driver_ids.add(driver.driver_id)
|
||||
|
||||
fallback_driver = self._legacy_send_drivers.get(route_key.platform)
|
||||
if fallback_driver is None:
|
||||
return []
|
||||
if fallback_driver is not None:
|
||||
descriptor = fallback_driver.descriptor
|
||||
account_matches = descriptor.account_id is None or route_key.account_id in (None, descriptor.account_id)
|
||||
scope_matches = descriptor.scope is None or route_key.scope in (None, descriptor.scope)
|
||||
if account_matches and scope_matches and fallback_driver.driver_id not in seen_driver_ids:
|
||||
drivers.append(fallback_driver)
|
||||
|
||||
descriptor = fallback_driver.descriptor
|
||||
if descriptor.account_id is not None and route_key.account_id not in (None, descriptor.account_id):
|
||||
return []
|
||||
if descriptor.scope is not None and route_key.scope not in (None, descriptor.scope):
|
||||
return []
|
||||
return [fallback_driver]
|
||||
return drivers
|
||||
|
||||
@staticmethod
|
||||
def build_route_key_from_message(message: "SessionMessage") -> RouteKey:
|
||||
|
||||
@@ -92,11 +92,24 @@ class OutboundTracker:
|
||||
raise ValueError("ttl_seconds 必须大于 0")
|
||||
|
||||
self._ttl_seconds = ttl_seconds
|
||||
self._pending: Dict[str, PendingOutboundRecord] = {}
|
||||
self._pending_expire_heap: List[Tuple[float, str]] = []
|
||||
self._pending: Dict[Tuple[str, str], PendingOutboundRecord] = {}
|
||||
self._pending_expire_heap: List[Tuple[float, str, str]] = []
|
||||
self._receipts_by_external_id: Dict[str, StoredDeliveryReceipt] = {}
|
||||
self._receipt_expire_heap: List[Tuple[float, str]] = []
|
||||
|
||||
@staticmethod
|
||||
def _build_pending_key(internal_message_id: str, driver_id: str) -> Tuple[str, str]:
|
||||
"""构造单条出站跟踪记录的唯一键。
|
||||
|
||||
Args:
|
||||
internal_message_id: 内部消息 ID。
|
||||
driver_id: 负责当前投递的驱动 ID。
|
||||
|
||||
Returns:
|
||||
Tuple[str, str]: ``(internal_message_id, driver_id)`` 组合键。
|
||||
"""
|
||||
return internal_message_id, driver_id
|
||||
|
||||
def begin_tracking(
|
||||
self,
|
||||
internal_message_id: str,
|
||||
@@ -116,13 +129,15 @@ class OutboundTracker:
|
||||
PendingOutboundRecord: 新创建的待完成记录。
|
||||
|
||||
Raises:
|
||||
ValueError: 当同一个 ``internal_message_id`` 已经存在未完成记录时抛出。
|
||||
ValueError: 当同一个 ``internal_message_id`` 与 ``driver_id`` 组合已经存在
|
||||
未完成记录时抛出。
|
||||
"""
|
||||
now = time.monotonic()
|
||||
self._cleanup_expired(now)
|
||||
pending_key = self._build_pending_key(internal_message_id, driver_id)
|
||||
|
||||
if internal_message_id in self._pending:
|
||||
raise ValueError(f"消息 {internal_message_id} 已存在未完成的出站跟踪记录")
|
||||
if pending_key in self._pending:
|
||||
raise ValueError(f"消息 {internal_message_id} 在驱动 {driver_id} 上已存在未完成的出站跟踪记录")
|
||||
|
||||
expires_at = now + self._ttl_seconds
|
||||
record = PendingOutboundRecord(
|
||||
@@ -133,8 +148,8 @@ class OutboundTracker:
|
||||
expires_at=expires_at,
|
||||
metadata=metadata or {},
|
||||
)
|
||||
self._pending[internal_message_id] = record
|
||||
heapq.heappush(self._pending_expire_heap, (expires_at, internal_message_id))
|
||||
self._pending[pending_key] = record
|
||||
heapq.heappush(self._pending_expire_heap, (expires_at, internal_message_id, driver_id))
|
||||
return record
|
||||
|
||||
def finish_tracking(self, receipt: DeliveryReceipt) -> Optional[PendingOutboundRecord]:
|
||||
@@ -149,7 +164,19 @@ class OutboundTracker:
|
||||
now = time.monotonic()
|
||||
self._cleanup_expired(now)
|
||||
|
||||
pending_record = self._pending.pop(receipt.internal_message_id, None)
|
||||
pending_record: Optional[PendingOutboundRecord] = None
|
||||
if receipt.driver_id:
|
||||
pending_key = self._build_pending_key(receipt.internal_message_id, receipt.driver_id)
|
||||
pending_record = self._pending.pop(pending_key, None)
|
||||
else:
|
||||
matched_records = [
|
||||
key
|
||||
for key, record in self._pending.items()
|
||||
if record.internal_message_id == receipt.internal_message_id
|
||||
]
|
||||
if len(matched_records) == 1:
|
||||
pending_record = self._pending.pop(matched_records[0], None)
|
||||
|
||||
if receipt.external_message_id:
|
||||
expires_at = now + self._ttl_seconds
|
||||
self._receipts_by_external_id[receipt.external_message_id] = StoredDeliveryReceipt(
|
||||
@@ -160,17 +187,33 @@ class OutboundTracker:
|
||||
heapq.heappush(self._receipt_expire_heap, (expires_at, receipt.external_message_id))
|
||||
return pending_record
|
||||
|
||||
def get_pending(self, internal_message_id: str) -> Optional[PendingOutboundRecord]:
|
||||
def get_pending(
|
||||
self,
|
||||
internal_message_id: str,
|
||||
driver_id: Optional[str] = None,
|
||||
) -> Optional[PendingOutboundRecord]:
|
||||
"""根据内部消息 ID 查询待完成记录。
|
||||
|
||||
Args:
|
||||
internal_message_id: 要查询的内部消息 ID。
|
||||
driver_id: 可选的驱动 ID;提供后仅返回该驱动上的待完成记录。
|
||||
|
||||
Returns:
|
||||
Optional[PendingOutboundRecord]: 若记录仍存在,则返回对应待完成记录。
|
||||
"""
|
||||
self._cleanup_expired(time.monotonic())
|
||||
return self._pending.get(internal_message_id)
|
||||
|
||||
if driver_id:
|
||||
return self._pending.get(self._build_pending_key(internal_message_id, driver_id))
|
||||
|
||||
matched_records = [
|
||||
record
|
||||
for record in self._pending.values()
|
||||
if record.internal_message_id == internal_message_id
|
||||
]
|
||||
if len(matched_records) == 1:
|
||||
return matched_records[0]
|
||||
return None
|
||||
|
||||
def get_receipt_by_external_id(self, external_message_id: str) -> Optional[DeliveryReceipt]:
|
||||
"""根据外部平台消息 ID 查询已完成回执。
|
||||
@@ -213,13 +256,14 @@ class OutboundTracker:
|
||||
``expires_at`` 对比,跳过这类旧节点。
|
||||
"""
|
||||
while self._pending_expire_heap and self._pending_expire_heap[0][0] <= now:
|
||||
expires_at, internal_message_id = heapq.heappop(self._pending_expire_heap)
|
||||
current_record = self._pending.get(internal_message_id)
|
||||
expires_at, internal_message_id, driver_id = heapq.heappop(self._pending_expire_heap)
|
||||
pending_key = self._build_pending_key(internal_message_id, driver_id)
|
||||
current_record = self._pending.get(pending_key)
|
||||
if current_record is None:
|
||||
continue
|
||||
if current_record.expires_at != expires_at:
|
||||
continue
|
||||
self._pending.pop(internal_message_id, None)
|
||||
self._pending.pop(pending_key, None)
|
||||
|
||||
def _cleanup_expired_receipts(self, now: float) -> None:
|
||||
"""清理已经过期的回执索引。
|
||||
|
||||
Reference in New Issue
Block a user