fix(channels): preserve legacy stream hook signatures

This commit is contained in:
Xubin Ren
2026-07-01 20:17:00 +08:00
parent c757c5466c
commit 03be51ade5
2 changed files with 216 additions and 25 deletions
+86 -25
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
import asyncio
import hashlib
import inspect
from collections.abc import Callable
from contextlib import suppress
from pathlib import Path
@@ -381,23 +382,95 @@ class ChannelManager:
except asyncio.CancelledError:
break
@staticmethod
def _accepts_keyword(callable_obj: Callable[..., Any], name: str) -> bool:
try:
signature = inspect.signature(callable_obj)
except (TypeError, ValueError):
return True
return any(
parameter.kind is inspect.Parameter.VAR_KEYWORD or parameter.name == name
for parameter in signature.parameters.values()
)
@classmethod
async def _send_reasoning_delta(cls, channel: BaseChannel, msg: OutboundMessage, event: ProgressEvent) -> None:
metadata = msg.metadata
kwargs: dict[str, Any] = {}
if cls._accepts_keyword(channel.send_reasoning_delta, "stream_id"):
kwargs["stream_id"] = event.stream_id
else:
metadata = dict(metadata or {})
metadata["_reasoning_delta"] = True
if event.stream_id is not None:
metadata["_stream_id"] = event.stream_id
await channel.send_reasoning_delta(
msg.chat_id,
msg.content,
metadata,
**kwargs,
)
@classmethod
async def _send_reasoning_end(cls, channel: BaseChannel, msg: OutboundMessage, event: ProgressEvent) -> None:
metadata = msg.metadata
kwargs: dict[str, Any] = {}
if cls._accepts_keyword(channel.send_reasoning_end, "stream_id"):
kwargs["stream_id"] = event.stream_id
else:
metadata = dict(metadata or {})
metadata["_reasoning_end"] = True
if event.stream_id is not None:
metadata["_stream_id"] = event.stream_id
await channel.send_reasoning_end(
msg.chat_id,
metadata,
**kwargs,
)
@classmethod
async def _send_stream_event(
cls,
channel: BaseChannel,
msg: OutboundMessage,
event: StreamDeltaEvent | StreamEndEvent,
) -> None:
metadata = msg.metadata
kwargs: dict[str, Any] = {}
if cls._accepts_keyword(channel.send_delta, "stream_id"):
kwargs["stream_id"] = event.stream_id
else:
metadata = dict(metadata or {})
if event.stream_id is not None:
metadata["_stream_id"] = event.stream_id
if isinstance(event, StreamEndEvent):
if cls._accepts_keyword(channel.send_delta, "stream_end"):
kwargs["stream_end"] = True
else:
metadata = dict(metadata or {})
metadata["_stream_end"] = True
if cls._accepts_keyword(channel.send_delta, "resuming"):
kwargs["resuming"] = event.resuming
elif not kwargs:
metadata = dict(metadata or {})
metadata["_stream_delta"] = True
await channel.send_delta(
msg.chat_id,
msg.content,
metadata,
**kwargs,
)
@staticmethod
async def _send_once(channel: BaseChannel, msg: OutboundMessage) -> None:
"""Send one outbound message without retry policy."""
event = outbound_event_from_message(msg)
if isinstance(event, ProgressEvent) and event.reasoning_end:
await channel.send_reasoning_end(
msg.chat_id,
msg.metadata,
stream_id=event.stream_id,
)
await ChannelManager._send_reasoning_end(channel, msg, event)
elif isinstance(event, ProgressEvent) and event.reasoning_delta:
await channel.send_reasoning_delta(
msg.chat_id,
msg.content,
msg.metadata,
stream_id=event.stream_id,
)
await ChannelManager._send_reasoning_delta(channel, msg, event)
elif isinstance(event, ProgressEvent) and event.reasoning:
# BaseChannel translates one-shot reasoning to a single delta +
# end pair so plugins only implement the streaming primitives.
@@ -409,21 +482,9 @@ class ChannelManager:
msg.metadata,
)
elif isinstance(event, StreamDeltaEvent):
await channel.send_delta(
msg.chat_id,
msg.content,
msg.metadata,
stream_id=event.stream_id,
)
await ChannelManager._send_stream_event(channel, msg, event)
elif isinstance(event, StreamEndEvent):
await channel.send_delta(
msg.chat_id,
msg.content,
msg.metadata,
stream_id=event.stream_id,
stream_end=True,
resuming=event.resuming,
)
await ChannelManager._send_stream_event(channel, msg, event)
elif not isinstance(event, StreamedResponseEvent):
await channel.send(msg)