feat: 增强日历功能并集成 AgentScope 代理服务
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from core.agentscope.events.agui_codec import to_agui_wire_event
|
||||
|
||||
|
||||
def test_maps_internal_text_delta_to_agui_wire_event() -> None:
|
||||
internal = {
|
||||
"id": "e1",
|
||||
"type": "text.delta",
|
||||
"threadId": "t1",
|
||||
"runId": "r1",
|
||||
"data": {"delta": "hel"},
|
||||
}
|
||||
|
||||
result = to_agui_wire_event(internal)
|
||||
|
||||
assert result["type"] == "TEXT_MESSAGE_CONTENT"
|
||||
assert result["threadId"] == "t1"
|
||||
assert result["runId"] == "r1"
|
||||
assert result["delta"] == "hel"
|
||||
|
||||
|
||||
def test_reserved_keys_in_data_cannot_override_wire_fields() -> None:
|
||||
internal = {
|
||||
"id": "e2",
|
||||
"type": "run.started",
|
||||
"threadId": "thread-1",
|
||||
"runId": "run-1",
|
||||
"data": {
|
||||
"type": "RUN_ERROR",
|
||||
"threadId": "thread-override",
|
||||
"runId": "run-override",
|
||||
"message": "ok",
|
||||
},
|
||||
}
|
||||
|
||||
result = to_agui_wire_event(internal)
|
||||
|
||||
assert result["type"] == "RUN_STARTED"
|
||||
assert result["threadId"] == "thread-1"
|
||||
assert result["runId"] == "run-1"
|
||||
assert result["message"] == "ok"
|
||||
@@ -0,0 +1,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from core.agentscope.events.pipeline import AgentScopeEventPipeline
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pipeline_orders_codec_persist_publish() -> None:
|
||||
calls: list[str] = []
|
||||
|
||||
class _Codec:
|
||||
def to_wire(self, event: dict[str, object]) -> dict[str, object]:
|
||||
calls.append("codec")
|
||||
return {"type": "RUN_STARTED", **event}
|
||||
|
||||
class _Store:
|
||||
async def persist(self, event: dict[str, object]) -> None:
|
||||
calls.append("persist")
|
||||
assert event["type"] == "RUN_STARTED"
|
||||
|
||||
class _Bus:
|
||||
async def publish(self, *, session_id: str, event: dict[str, object]) -> str:
|
||||
calls.append("publish")
|
||||
assert session_id == "thread-1"
|
||||
return "1-0"
|
||||
|
||||
pipeline = AgentScopeEventPipeline(codec=_Codec(), store=_Store(), bus=_Bus())
|
||||
cursor = await pipeline.emit(session_id="thread-1", event={"id": "evt-1"})
|
||||
|
||||
assert cursor == "1-0"
|
||||
assert calls == ["codec", "persist", "publish"]
|
||||
@@ -0,0 +1,71 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from core.agentscope.events.redis_bus import RedisStreamBus
|
||||
|
||||
|
||||
class _FakeRedis:
|
||||
def __init__(self) -> None:
|
||||
self._rows: list[tuple[str, str]] = []
|
||||
|
||||
def xadd(self, _stream: str, fields: dict[str, str]) -> str:
|
||||
cursor = f"{len(self._rows) + 1}-0"
|
||||
self._rows.append((cursor, fields["event"]))
|
||||
return cursor
|
||||
|
||||
def xread(
|
||||
self,
|
||||
streams: dict[str, str],
|
||||
count: int,
|
||||
block: int,
|
||||
) -> list[tuple[str, list[tuple[str, dict[str, str]]]]]:
|
||||
del count, block
|
||||
stream_name, last = next(iter(streams.items()))
|
||||
rows: list[tuple[str, dict[str, str]]] = []
|
||||
for cursor, payload in self._rows:
|
||||
if cursor > last:
|
||||
rows.append((cursor, {"event": payload}))
|
||||
return [(stream_name, rows)]
|
||||
|
||||
|
||||
class _FakeRedisBytes:
|
||||
def __init__(self) -> None:
|
||||
self._rows: list[tuple[str, str]] = []
|
||||
|
||||
def xadd(self, _stream: str, fields: dict[str, str]) -> str:
|
||||
cursor = f"{len(self._rows) + 1}-0"
|
||||
self._rows.append((cursor, fields["event"]))
|
||||
return cursor
|
||||
|
||||
def xread(
|
||||
self,
|
||||
streams: dict[str, str],
|
||||
count: int,
|
||||
block: int,
|
||||
) -> list[tuple[str, list[tuple[str, dict[str, bytes]]]]]:
|
||||
del count, block
|
||||
stream_name, last = next(iter(streams.items()))
|
||||
rows: list[tuple[str, dict[str, bytes]]] = []
|
||||
for cursor, payload in self._rows:
|
||||
if cursor > last:
|
||||
rows.append((cursor, {"event": payload.encode("utf-8")}))
|
||||
return [(stream_name, rows)]
|
||||
|
||||
|
||||
async def test_publish_then_read_after_cursor() -> None:
|
||||
bus = RedisStreamBus(client=_FakeRedis(), stream_prefix="agent.events")
|
||||
|
||||
first_cursor = await bus.publish(
|
||||
session_id="thread-1", event={"type": "RUN_STARTED"}
|
||||
)
|
||||
await bus.publish(session_id="thread-1", event={"type": "RUN_FINISHED"})
|
||||
|
||||
rows = await bus.read(session_id="thread-1", last_event_id=first_cursor)
|
||||
assert len(rows) == 1
|
||||
assert rows[0]["event"]["type"] == "RUN_FINISHED"
|
||||
|
||||
|
||||
async def test_read_supports_bytes_payload() -> None:
|
||||
bus = RedisStreamBus(client=_FakeRedisBytes(), stream_prefix="agent.events")
|
||||
await bus.publish(session_id="thread-1", event={"type": "RUN_STARTED"})
|
||||
rows = await bus.read(session_id="thread-1", last_event_id=None)
|
||||
assert rows[0]["event"]["type"] == "RUN_STARTED"
|
||||
@@ -0,0 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from core.agentscope.events.sse import to_sse_event
|
||||
|
||||
|
||||
def test_sse_frame_contains_event_and_json_payload() -> None:
|
||||
payload = {"type": "RUN_STARTED", "threadId": "t1", "runId": "r1"}
|
||||
|
||||
frame = to_sse_event("1-0", payload)
|
||||
|
||||
assert frame.startswith("id: 1-0\n")
|
||||
assert "event: RUN_STARTED\n" in frame
|
||||
assert frame.endswith("\n\n")
|
||||
|
||||
data_line = [line for line in frame.splitlines() if line.startswith("data: ")][0]
|
||||
parsed = json.loads(data_line[len("data: ") :])
|
||||
assert parsed["threadId"] == "t1"
|
||||
|
||||
|
||||
def test_sse_sanitizes_stream_id_newlines() -> None:
|
||||
payload = {"type": "RUN_STARTED"}
|
||||
frame = to_sse_event("1-0\nmalicious: yes", payload)
|
||||
assert frame.startswith("id: 1-0malicious: yes\n")
|
||||
Reference in New Issue
Block a user