mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Simplify Python hosting core (#6492)
Remove linking, multicast, durable delivery, and host push machinery from the v1 hosting core. Keep those scenarios in a proposed follow-up ADR and update channel packages, samples, docs, tests, and workspace metadata around the smaller host/channel contract. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
parent
e5a6e35843
commit
36ce0950e4
@@ -9,11 +9,11 @@ import json
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
from collections.abc import Awaitable, Callable, Coroutine, Mapping, Sequence
|
||||
from collections.abc import Callable, Coroutine, Mapping, Sequence
|
||||
from typing import Any, cast
|
||||
|
||||
import httpx
|
||||
from agent_framework import AgentResponse, AgentResponseUpdate, Content, Message, ResponseStream
|
||||
from agent_framework import AgentResponse, AgentResponseUpdate, ResponseStream
|
||||
from agent_framework_hosting import (
|
||||
ChannelCommand,
|
||||
ChannelCommandContext,
|
||||
@@ -24,10 +24,8 @@ from agent_framework_hosting import (
|
||||
ChannelResponseHook,
|
||||
ChannelRunHook,
|
||||
ChannelSession,
|
||||
ChannelStreamTransformHook,
|
||||
ChannelStreamUpdateHook,
|
||||
HostedRunResult,
|
||||
apply_channel_response_hook,
|
||||
apply_run_hook,
|
||||
)
|
||||
from nacl.exceptions import BadSignatureError
|
||||
from nacl.signing import VerifyKey
|
||||
@@ -75,11 +73,6 @@ def _default_isolation_key(interaction: DiscordInteraction) -> str:
|
||||
return discord_isolation_key(guild_id, channel_id, user_id)
|
||||
|
||||
|
||||
def _text_result(text: str) -> HostedRunResult[AgentResponse]:
|
||||
"""Build a host delivery payload from text accumulated by this channel."""
|
||||
return HostedRunResult(AgentResponse(messages=[Message(role="assistant", contents=[Content.from_text(text=text)])]))
|
||||
|
||||
|
||||
class DiscordChannel:
|
||||
"""Discord channel backed by signed HTTP Interactions."""
|
||||
|
||||
@@ -100,7 +93,7 @@ class DiscordChannel:
|
||||
commands: Sequence[ChannelCommand] | None = None,
|
||||
run_hook: ChannelRunHook | None = None,
|
||||
response_hook: ChannelResponseHook | None = None,
|
||||
stream_transform_hook: ChannelStreamTransformHook | None = None,
|
||||
stream_update_hook: ChannelStreamUpdateHook | None = None,
|
||||
streaming: bool = False,
|
||||
isolation_key_factory: DiscordIsolationKeyFactory | None = None,
|
||||
skip_signature_verification: bool = False,
|
||||
@@ -133,7 +126,7 @@ class DiscordChannel:
|
||||
it reaches the host.
|
||||
response_hook: Optional hook that can rewrite the hosted result
|
||||
before the originating Discord response is serialized.
|
||||
stream_transform_hook: Optional per-update transform hook applied
|
||||
stream_update_hook: Optional per-update hook applied
|
||||
while streaming.
|
||||
streaming: Whether the agent command should call ``run_stream``
|
||||
and edit the original interaction response as deltas arrive.
|
||||
@@ -163,7 +156,7 @@ class DiscordChannel:
|
||||
self._command_by_name = {command.name: command for command in self._commands}
|
||||
self._run_hook = run_hook
|
||||
self.response_hook = response_hook
|
||||
self._stream_transform_hook = stream_transform_hook
|
||||
self._stream_update_hook = stream_update_hook
|
||||
self._streaming = streaming
|
||||
self._isolation_key_factory = isolation_key_factory or _default_isolation_key
|
||||
self._skip_signature_verification = skip_signature_verification
|
||||
@@ -190,25 +183,6 @@ class DiscordChannel:
|
||||
on_shutdown=[self._on_shutdown],
|
||||
)
|
||||
|
||||
async def push(self, identity: ChannelIdentity, payload: HostedRunResult[Any]) -> None:
|
||||
"""Push a hosted result to a Discord channel.
|
||||
|
||||
Args:
|
||||
identity: Destination identity. ``identity.attributes`` must carry
|
||||
``channel_id``.
|
||||
payload: Hosted run result to render as Discord message text.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If the channel has no bot token for Discord REST.
|
||||
ValueError: If ``channel_id`` is missing from the identity.
|
||||
"""
|
||||
channel_id = _string_or_none(identity.attributes.get("channel_id"))
|
||||
if channel_id is None:
|
||||
raise ValueError("Discord push requires identity.attributes['channel_id']")
|
||||
if self.bot_token is None:
|
||||
raise RuntimeError("DiscordChannel.push requires bot_token to send channel messages")
|
||||
await self._send_channel_messages(channel_id, _payload_text(payload))
|
||||
|
||||
async def _on_startup(self) -> None:
|
||||
"""Open the Discord REST client and optionally register slash commands."""
|
||||
self._ensure_http()
|
||||
@@ -297,23 +271,17 @@ class DiscordChannel:
|
||||
input_value=prompt,
|
||||
stream=self._streaming,
|
||||
)
|
||||
if self._run_hook is not None:
|
||||
request = await apply_run_hook(
|
||||
self._run_hook,
|
||||
request,
|
||||
target=self._ctx.target,
|
||||
protocol_request=interaction,
|
||||
)
|
||||
if request.stream:
|
||||
await self._run_streaming(request, token)
|
||||
await self._run_streaming(request, token, protocol_request=interaction)
|
||||
return
|
||||
result = await self._ctx.run(request)
|
||||
include_originating = await self._ctx.deliver_response(request, result)
|
||||
if include_originating:
|
||||
result = await apply_channel_response_hook(self, result, request=request, originating=True)
|
||||
await self._edit_original_with_result(token, result)
|
||||
else:
|
||||
await self._edit_original(token, "Sent.")
|
||||
result = await self._ctx.run(
|
||||
request=request,
|
||||
run_hook=self._run_hook,
|
||||
protocol_request=interaction,
|
||||
response_hook=self.response_hook,
|
||||
channel_name=self.name,
|
||||
)
|
||||
await self._edit_original_with_result(token, result)
|
||||
|
||||
async def _run_channel_command(
|
||||
self,
|
||||
@@ -333,23 +301,23 @@ class DiscordChannel:
|
||||
if not reply.sent:
|
||||
await self._edit_original(token, "Done.")
|
||||
|
||||
async def _run_streaming(self, request: ChannelRequest, token: str) -> None:
|
||||
async def _run_streaming(
|
||||
self, request: ChannelRequest, token: str, *, protocol_request: DiscordInteraction | None = None
|
||||
) -> None:
|
||||
if self._ctx is None:
|
||||
raise RuntimeError("DiscordChannel was not contributed to a host.")
|
||||
stream: ResponseStream[AgentResponseUpdate, AgentResponse] = self._ctx.run_stream(request)
|
||||
stream: ResponseStream[AgentResponseUpdate, AgentResponse] = await self._ctx.run_stream(
|
||||
request,
|
||||
run_hook=self._run_hook,
|
||||
protocol_request=protocol_request,
|
||||
stream_update_hook=self._stream_update_hook,
|
||||
response_hook=self.response_hook,
|
||||
channel_name=self.name,
|
||||
)
|
||||
accumulated: list[str] = []
|
||||
last_edit = 0.0
|
||||
async for update in stream:
|
||||
transformed: AgentResponseUpdate | None = update
|
||||
if self._stream_transform_hook is not None:
|
||||
maybe = self._stream_transform_hook(update)
|
||||
if isinstance(maybe, Awaitable):
|
||||
transformed = await cast("Awaitable[AgentResponseUpdate | None]", maybe)
|
||||
else:
|
||||
transformed = maybe
|
||||
if transformed is None:
|
||||
continue
|
||||
chunk = _update_text(transformed)
|
||||
chunk = _update_text(update)
|
||||
if not chunk:
|
||||
continue
|
||||
accumulated.append(chunk)
|
||||
@@ -358,13 +326,8 @@ class DiscordChannel:
|
||||
await self._edit_original(token, _stream_preview_content("".join(accumulated)))
|
||||
last_edit = now
|
||||
|
||||
final = _text_result("".join(accumulated))
|
||||
include_originating = await self._ctx.deliver_response(request, final)
|
||||
if include_originating:
|
||||
final = await apply_channel_response_hook(self, final, request=request, originating=True)
|
||||
await self._edit_original_with_result(token, final)
|
||||
else:
|
||||
await self._edit_original(token, "Sent.")
|
||||
final_response = await stream.get_final_response()
|
||||
await self._edit_original_with_result(token, HostedRunResult(final_response))
|
||||
|
||||
def _build_request(
|
||||
self,
|
||||
@@ -478,16 +441,6 @@ class DiscordChannel:
|
||||
)
|
||||
_raise_for_discord_error(response, "send interaction follow-up")
|
||||
|
||||
async def _send_channel_messages(self, channel_id: str, content: str) -> None:
|
||||
http = self._ensure_http()
|
||||
for chunk in _split_content(content):
|
||||
response = await http.post(
|
||||
f"/channels/{channel_id}/messages",
|
||||
headers=self._bot_headers(),
|
||||
json={"content": chunk},
|
||||
)
|
||||
_raise_for_discord_error(response, "send channel message")
|
||||
|
||||
def _bot_headers(self) -> dict[str, str]:
|
||||
if self.bot_token is None:
|
||||
raise RuntimeError("Discord bot token is required for this operation")
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from collections.abc import AsyncIterator
|
||||
from collections.abc import AsyncIterator, Awaitable
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
@@ -13,7 +13,6 @@ from agent_framework_hosting import (
|
||||
ChannelCommand,
|
||||
ChannelCommandContext,
|
||||
ChannelRequest,
|
||||
ChannelResponseContext,
|
||||
HostedRunResult,
|
||||
)
|
||||
from nacl.signing import SigningKey
|
||||
@@ -60,39 +59,95 @@ def _headers(signing_key: SigningKey, body: bytes) -> dict[str, str]:
|
||||
|
||||
|
||||
class _FakeContext:
|
||||
def __init__(self, *, text: str = "agent reply", include_originating: bool = True) -> None:
|
||||
def __init__(self, *, text: str = "agent reply") -> None:
|
||||
self.target = object()
|
||||
self.text = text
|
||||
self.include_originating = include_originating
|
||||
self.requests: list[ChannelRequest] = []
|
||||
self.delivered: list[tuple[ChannelRequest, HostedRunResult[Any]]] = []
|
||||
self.stream: _FakeStream | None = None
|
||||
self.fake_stream: _FakeStream | None = None
|
||||
|
||||
async def run(self, request: ChannelRequest) -> HostedRunResult[AgentResponse]:
|
||||
async def run(
|
||||
self,
|
||||
request: ChannelRequest,
|
||||
*,
|
||||
run_hook: Any | None = None,
|
||||
protocol_request: Any | None = None,
|
||||
response_hook: Any | None = None,
|
||||
channel_name: str | None = None,
|
||||
) -> HostedRunResult[AgentResponse]:
|
||||
if run_hook is not None:
|
||||
maybe_request = run_hook(request, target=self.target, protocol_request=protocol_request)
|
||||
if isinstance(maybe_request, Awaitable):
|
||||
request = await maybe_request
|
||||
else:
|
||||
request = maybe_request
|
||||
self.requests.append(request)
|
||||
return _run_result(self.text)
|
||||
result = _run_result(self.text)
|
||||
if response_hook is not None:
|
||||
maybe_result = response_hook(result, request=request, channel_name=channel_name or request.channel)
|
||||
if isinstance(maybe_result, Awaitable):
|
||||
return await maybe_result
|
||||
return maybe_result
|
||||
return result
|
||||
|
||||
def run_stream(self, request: ChannelRequest) -> _FakeStream:
|
||||
async def run_stream(
|
||||
self,
|
||||
request: ChannelRequest,
|
||||
*,
|
||||
run_hook: Any | None = None,
|
||||
protocol_request: Any | None = None,
|
||||
stream_update_hook: Any | None = None,
|
||||
response_hook: Any | None = None,
|
||||
channel_name: str | None = None,
|
||||
) -> _FakeStream:
|
||||
if run_hook is not None:
|
||||
maybe_request = run_hook(request, target=self.target, protocol_request=protocol_request)
|
||||
if isinstance(maybe_request, Awaitable):
|
||||
request = await maybe_request
|
||||
else:
|
||||
request = maybe_request
|
||||
self.requests.append(request)
|
||||
if self.stream is None:
|
||||
self.stream = _FakeStream(["a", "b"])
|
||||
return self.stream
|
||||
|
||||
async def deliver_response(self, request: ChannelRequest, payload: HostedRunResult[Any]) -> bool:
|
||||
self.delivered.append((request, payload))
|
||||
return self.include_originating
|
||||
if self.fake_stream is None:
|
||||
self.fake_stream = _FakeStream(["a", "b"])
|
||||
if stream_update_hook is not None:
|
||||
self.fake_stream.transform = stream_update_hook
|
||||
if response_hook is not None:
|
||||
self.fake_stream.response_hook = response_hook
|
||||
self.fake_stream.request = request
|
||||
self.fake_stream.channel_name = channel_name or request.channel
|
||||
return self.fake_stream
|
||||
|
||||
|
||||
class _FakeStream:
|
||||
def __init__(self, chunks: list[str]) -> None:
|
||||
self._chunks = chunks
|
||||
self.transform: Any | None = None
|
||||
self.response_hook: Any | None = None
|
||||
self.request: ChannelRequest | None = None
|
||||
self.channel_name: str | None = None
|
||||
|
||||
def __aiter__(self) -> AsyncIterator[AgentResponseUpdate]:
|
||||
return self._iter()
|
||||
|
||||
async def _iter(self) -> AsyncIterator[AgentResponseUpdate]:
|
||||
for chunk in self._chunks:
|
||||
yield AgentResponseUpdate(contents=[Content.from_text(text=chunk)], role="assistant")
|
||||
update = AgentResponseUpdate(contents=[Content.from_text(text=chunk)], role="assistant")
|
||||
if self.transform is not None:
|
||||
transformed = self.transform(update)
|
||||
if isinstance(transformed, Awaitable):
|
||||
transformed = await transformed
|
||||
if transformed is None:
|
||||
continue
|
||||
update = transformed
|
||||
yield update
|
||||
|
||||
async def get_final_response(self) -> AgentResponse:
|
||||
result = _run_result("".join(self._chunks))
|
||||
if self.response_hook is None:
|
||||
return result.result
|
||||
shaped = self.response_hook(result, request=self.request, channel_name=self.channel_name)
|
||||
if isinstance(shaped, Awaitable):
|
||||
shaped = await shaped
|
||||
return shaped.result
|
||||
|
||||
|
||||
class _DiscordRecorder:
|
||||
@@ -212,7 +267,6 @@ async def test_agent_command_runs_host_and_edits_original_response() -> None:
|
||||
assert context.requests[0].identity is not None
|
||||
assert context.requests[0].identity.native_id == "user-1"
|
||||
assert context.requests[0].identity.attributes["channel_id"] == "channel-1"
|
||||
assert len(context.delivered) == 1
|
||||
assert recorder.requests[0].method == "PATCH"
|
||||
assert recorder.requests[0].url.path == "/webhooks/app-1/token/messages/@original"
|
||||
assert recorder.json_payloads[0] == {"content": "agent says hi"}
|
||||
@@ -232,7 +286,6 @@ async def test_run_hook_can_rewrite_agent_request() -> None:
|
||||
attributes=request.attributes,
|
||||
stream=request.stream,
|
||||
identity=request.identity,
|
||||
response_target=request.response_target,
|
||||
)
|
||||
|
||||
channel = DiscordChannel(
|
||||
@@ -254,9 +307,9 @@ async def test_response_hook_rewrites_originating_reply() -> None:
|
||||
recorder = _DiscordRecorder()
|
||||
context = _FakeContext(text="original")
|
||||
|
||||
async def hook(result: HostedRunResult[Any], *, context: ChannelResponseContext) -> HostedRunResult[Any]:
|
||||
assert context.originating is True
|
||||
async def hook(result: HostedRunResult[Any], **kwargs: Any) -> HostedRunResult[Any]:
|
||||
assert result.result.text == "original"
|
||||
assert kwargs["channel_name"] == "discord"
|
||||
return _run_result("rewritten")
|
||||
|
||||
channel = DiscordChannel(
|
||||
@@ -274,23 +327,6 @@ async def test_response_hook_rewrites_originating_reply() -> None:
|
||||
assert recorder.json_payloads[-1] == {"content": "rewritten"}
|
||||
|
||||
|
||||
async def test_deliver_response_false_acknowledges_without_originating_payload() -> None:
|
||||
recorder = _DiscordRecorder()
|
||||
context = _FakeContext(text="fanout only", include_originating=False)
|
||||
channel = DiscordChannel(
|
||||
application_id="app-1",
|
||||
public_key=SigningKey.generate().verify_key.encode().hex(),
|
||||
register_commands=False,
|
||||
api_base_url="https://discord.test",
|
||||
)
|
||||
channel.contribute(context) # type: ignore[arg-type]
|
||||
channel._http = httpx.AsyncClient(base_url="https://discord.test", transport=recorder.transport())
|
||||
|
||||
await channel._run_agent_command(_interaction(), "token")
|
||||
|
||||
assert recorder.json_payloads[-1] == {"content": "Sent."}
|
||||
|
||||
|
||||
async def test_missing_prompt_edits_original_without_calling_host() -> None:
|
||||
recorder = _DiscordRecorder()
|
||||
context = _FakeContext(text="should not run")
|
||||
@@ -513,75 +549,6 @@ async def test_originating_reply_sends_followup_chunks() -> None:
|
||||
assert [len(payload["content"]) for payload in recorder.json_payloads] == [2000, 1]
|
||||
|
||||
|
||||
async def test_push_requires_channel_id_and_sends_chunked_messages() -> None:
|
||||
recorder = _DiscordRecorder()
|
||||
channel = DiscordChannel(
|
||||
application_id="app-1",
|
||||
public_key=SigningKey.generate().verify_key.encode().hex(),
|
||||
bot_token="bot-token",
|
||||
register_commands=False,
|
||||
api_base_url="https://discord.test",
|
||||
)
|
||||
channel._http = httpx.AsyncClient(base_url="https://discord.test", transport=recorder.transport())
|
||||
|
||||
await channel.push(
|
||||
identity=channel._identity_from_interaction(_interaction()), # pyright: ignore[reportPrivateUsage]
|
||||
payload=_run_result("a" * 2001),
|
||||
)
|
||||
|
||||
assert [request.url.path for request in recorder.requests] == [
|
||||
"/channels/channel-1/messages",
|
||||
"/channels/channel-1/messages",
|
||||
]
|
||||
assert [len(payload["content"]) for payload in recorder.json_payloads] == [2000, 1]
|
||||
|
||||
|
||||
async def test_push_renders_no_response_for_unknown_payload_shape() -> None:
|
||||
recorder = _DiscordRecorder()
|
||||
channel = DiscordChannel(
|
||||
application_id="app-1",
|
||||
public_key=SigningKey.generate().verify_key.encode().hex(),
|
||||
bot_token="bot-token",
|
||||
register_commands=False,
|
||||
api_base_url="https://discord.test",
|
||||
)
|
||||
channel._http = httpx.AsyncClient(base_url="https://discord.test", transport=recorder.transport())
|
||||
|
||||
await channel.push(
|
||||
identity=channel._identity_from_interaction(_interaction()), # pyright: ignore[reportPrivateUsage]
|
||||
payload=HostedRunResult(object()),
|
||||
)
|
||||
|
||||
assert recorder.json_payloads == [{"content": "(no response)"}]
|
||||
|
||||
|
||||
async def test_push_requires_bot_token_and_channel_id() -> None:
|
||||
identity = DiscordChannel(
|
||||
application_id="app-1",
|
||||
public_key=SigningKey.generate().verify_key.encode().hex(),
|
||||
register_commands=False,
|
||||
)._identity_from_interaction(_interaction()) # pyright: ignore[reportPrivateUsage]
|
||||
no_bot_token = DiscordChannel(
|
||||
application_id="app-1",
|
||||
public_key=SigningKey.generate().verify_key.encode().hex(),
|
||||
register_commands=False,
|
||||
)
|
||||
no_channel_id = DiscordChannel(
|
||||
application_id="app-1",
|
||||
public_key=SigningKey.generate().verify_key.encode().hex(),
|
||||
bot_token="bot-token",
|
||||
register_commands=False,
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError, match="bot_token"):
|
||||
await no_bot_token.push(identity=identity, payload=_run_result("hello"))
|
||||
with pytest.raises(ValueError, match="channel_id"):
|
||||
await no_channel_id.push(
|
||||
identity=type(identity)(channel=identity.channel, native_id=identity.native_id, attributes={}),
|
||||
payload=_run_result("hello"),
|
||||
)
|
||||
|
||||
|
||||
async def test_streaming_edits_original_and_delivers_final_response() -> None:
|
||||
recorder = _DiscordRecorder()
|
||||
context = _FakeContext()
|
||||
@@ -599,14 +566,12 @@ async def test_streaming_edits_original_and_delivers_final_response() -> None:
|
||||
await channel._run_agent_command(_interaction(), "token")
|
||||
|
||||
assert [payload["content"] for payload in recorder.json_payloads] == ["a", "ab", "ab"]
|
||||
assert len(context.delivered) == 1
|
||||
assert context.delivered[0][1].result.text == "ab"
|
||||
|
||||
|
||||
async def test_streaming_preview_is_limited_and_final_reply_is_chunked() -> None:
|
||||
recorder = _DiscordRecorder()
|
||||
context = _FakeContext()
|
||||
context.stream = _FakeStream(["a" * 2001])
|
||||
context.fake_stream = _FakeStream(["a" * 2001])
|
||||
channel = DiscordChannel(
|
||||
application_id="app-1",
|
||||
public_key=SigningKey.generate().verify_key.encode().hex(),
|
||||
@@ -622,12 +587,11 @@ async def test_streaming_preview_is_limited_and_final_reply_is_chunked() -> None
|
||||
|
||||
assert [request.method for request in recorder.requests] == ["PATCH", "PATCH", "POST"]
|
||||
assert [len(payload["content"]) for payload in recorder.json_payloads] == [2000, 2000, 1]
|
||||
assert len(context.delivered[0][1].result.text) == 2001
|
||||
|
||||
|
||||
async def test_stream_transform_hook_can_drop_updates_and_disable_originating_reply() -> None:
|
||||
async def test_stream_update_hook_can_drop_updates() -> None:
|
||||
recorder = _DiscordRecorder()
|
||||
context = _FakeContext(include_originating=False)
|
||||
context = _FakeContext()
|
||||
|
||||
async def hook(update: AgentResponseUpdate) -> AgentResponseUpdate | None:
|
||||
if update.text == "a":
|
||||
@@ -639,7 +603,7 @@ async def test_stream_transform_hook_can_drop_updates_and_disable_originating_re
|
||||
public_key=SigningKey.generate().verify_key.encode().hex(),
|
||||
register_commands=False,
|
||||
streaming=True,
|
||||
stream_transform_hook=hook,
|
||||
stream_update_hook=hook,
|
||||
edit_interval=0,
|
||||
api_base_url="https://discord.test",
|
||||
)
|
||||
@@ -648,11 +612,10 @@ async def test_stream_transform_hook_can_drop_updates_and_disable_originating_re
|
||||
|
||||
await channel._run_agent_command(_interaction(), "token")
|
||||
|
||||
assert [payload["content"] for payload in recorder.json_payloads] == ["b", "Sent."]
|
||||
assert context.delivered[0][1].result.text == "b"
|
||||
assert [payload["content"] for payload in recorder.json_payloads] == ["b", "ab"]
|
||||
|
||||
|
||||
async def test_stream_transform_hook_can_synchronously_rewrite_updates() -> None:
|
||||
async def test_stream_update_hook_can_synchronously_rewrite_updates() -> None:
|
||||
recorder = _DiscordRecorder()
|
||||
context = _FakeContext()
|
||||
|
||||
@@ -664,7 +627,7 @@ async def test_stream_transform_hook_can_synchronously_rewrite_updates() -> None
|
||||
public_key=SigningKey.generate().verify_key.encode().hex(),
|
||||
register_commands=False,
|
||||
streaming=True,
|
||||
stream_transform_hook=hook,
|
||||
stream_update_hook=hook,
|
||||
edit_interval=0,
|
||||
api_base_url="https://discord.test",
|
||||
)
|
||||
@@ -673,7 +636,7 @@ async def test_stream_transform_hook_can_synchronously_rewrite_updates() -> None
|
||||
|
||||
await channel._run_agent_command(_interaction(), "token")
|
||||
|
||||
assert [payload["content"] for payload in recorder.json_payloads] == ["x", "xx", "xx"]
|
||||
assert [payload["content"] for payload in recorder.json_payloads] == ["x", "xx", "ab"]
|
||||
|
||||
|
||||
async def _noop() -> None:
|
||||
|
||||
Reference in New Issue
Block a user