feat: 增强日历功能并集成 AgentScope 代理服务

This commit is contained in:
qzl
2026-03-11 17:16:11 +08:00
parent e20e7d2a02
commit 85b314cf64
53 changed files with 3642 additions and 297 deletions
@@ -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")