mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-19 10:22:04 +08:00
feat: 更新文档和代码,优化运行时模块的导出,确保高级原语的稳定性和兼容性
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
- 2026-03-13: Old Star docs under `docs/zh/dev/star/` describe end-to-end legacy behavior, not just import surfaces. Current compat layer can import `AstrMessageEvent`, `MessageChain`, and some `filter.*` helpers, but handler result consumption is still effectively plain-text only and many documented legacy features remain absent or partial, including command groups, lifecycle/LLM hooks, session waiters, rich media helper constructors, config schema loading, and persona/provider management. Do not treat "type exists" as "old plugin behavior is compatible"; verify the runtime path end to end before declaring parity.
|
||||
- 2026-03-13: Old Star docs and examples frequently import message components via `astrbot.api.message_components`, not only `astrbot.api.message`. When checking message compatibility, verify the dedicated `api.message_components` import path, legacy constructor aliases like `At(qq=...)` / `Node(uin=..., name=...)`, and helper factories such as `Image.fromURL()` before declaring the message compat surface complete.
|
||||
- 2026-03-13: `api.message.components.BaseMessageComponent.to_dict()` must emit JSON-ready primitive values, not raw `Enum` members. Leaving `ComponentType` objects in payloads only looks harmless when a later JSON serializer fixes them; it breaks direct mock assertions, in-process capability routing, and any non-JSON send path.
|
||||
- 2026-03-13: Keep `astrbot_sdk.runtime` root exports narrow. `Peer` / `Transport` / `CapabilityRouter` / `HandlerDispatcher` are reasonable advanced runtime primitives, but loader/bootstrap data structures and orchestration helpers (`LoadedPlugin`, `PluginEnvironmentManager`, `WorkerSession`, `run_supervisor`, etc.) should stay in their submodules instead of becoming accidental root-level stable API.
|
||||
|
||||
# 开发命令
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
- 2026-03-13: Old Star docs under `docs/zh/dev/star/` describe end-to-end legacy behavior, not just import surfaces. Current compat layer can import `AstrMessageEvent`, `MessageChain`, and some `filter.*` helpers, but handler result consumption is still effectively plain-text only and many documented legacy features remain absent or partial, including command groups, lifecycle/LLM hooks, session waiters, rich media helper constructors, config schema loading, and persona/provider management. Do not treat "type exists" as "old plugin behavior is compatible"; verify the runtime path end to end before declaring parity.
|
||||
- 2026-03-13: Old Star docs and examples frequently import message components via `astrbot.api.message_components`, not only `astrbot.api.message`. When checking message compatibility, verify the dedicated `api.message_components` import path, legacy constructor aliases like `At(qq=...)` / `Node(uin=..., name=...)`, and helper factories such as `Image.fromURL()` before declaring the message compat surface complete.
|
||||
- 2026-03-13: `api.message.components.BaseMessageComponent.to_dict()` must emit JSON-ready primitive values, not raw `Enum` members. Leaving `ComponentType` objects in payloads only looks harmless when a later JSON serializer fixes them; it breaks direct mock assertions, in-process capability routing, and any non-JSON send path.
|
||||
- 2026-03-13: Keep `astrbot_sdk.runtime` root exports narrow. `Peer` / `Transport` / `CapabilityRouter` / `HandlerDispatcher` are reasonable advanced runtime primitives, but loader/bootstrap data structures and orchestration helpers (`LoadedPlugin`, `PluginEnvironmentManager`, `WorkerSession`, `run_supervisor`, etc.) should stay in their submodules instead of becoming accidental root-level stable API.
|
||||
|
||||
# 开发命令
|
||||
|
||||
|
||||
@@ -1,155 +1,13 @@
|
||||
"""运行时模块。
|
||||
"""AstrBot SDK 的高级运行时原语。
|
||||
|
||||
定义 AstrBot SDK 的运行时架构,包括插件加载、能力路由、处理器分发和通信抽象。
|
||||
|
||||
架构说明:
|
||||
旧版:
|
||||
- 目录结构复杂:api/, rpc/, stars/ 等多个子目录
|
||||
- 使用 JSON-RPC 2.0 协议进行通信
|
||||
- StarManager 负责插件发现和加载
|
||||
- StarRunner 负责处理器执行
|
||||
- Galaxy 负责虚拟星层管理
|
||||
- 传输层分离为 client/server 两套实现
|
||||
|
||||
新版:
|
||||
- 目录结构精简:仅 6 个核心文件
|
||||
- 使用自描述协议进行通信
|
||||
- Peer 统一处理协议层消息收发
|
||||
- Transport 抽象传输层,支持多种实现
|
||||
- CapabilityRouter 注册和路由能力调用
|
||||
- HandlerDispatcher 分发处理器调用
|
||||
- SupervisorRuntime 管理多 Worker 会话
|
||||
|
||||
核心概念对比:
|
||||
旧版概念:
|
||||
- StarManager: 插件发现和加载
|
||||
- StarRunner: 处理器执行
|
||||
- Galaxy: 虚拟星层管理
|
||||
- JSONRPCServer/Client: JSON-RPC 通信
|
||||
- HandshakeHandler: 握手处理
|
||||
- HandlerExecutor: 处理器执行
|
||||
|
||||
新版概念:
|
||||
- Peer: 协议对等端,统一处理消息
|
||||
- Transport: 传输层抽象
|
||||
- CapabilityRouter: 能力路由
|
||||
- HandlerDispatcher: 处理器分发
|
||||
- SupervisorRuntime: 多 Worker 管理
|
||||
- WorkerSession: 单个 Worker 会话
|
||||
- PluginWorkerRuntime: 插件 Worker 运行时
|
||||
|
||||
通信流程对比:
|
||||
旧版 JSON-RPC 流程:
|
||||
1. Core -> Plugin: {"method": "handshake", ...}
|
||||
2. Plugin -> Core: {"result": {"handlers": [...]}}
|
||||
3. Core -> Plugin: {"method": "call_handler", "params": {...}}
|
||||
4. Plugin -> Core: {"method": "handler_stream_start", ...}
|
||||
5. Plugin -> Core: {"method": "handler_stream_update", ...}
|
||||
6. Plugin -> Core: {"method": "handler_stream_end", ...}
|
||||
|
||||
新版协议流程:
|
||||
1. Plugin -> Core: {"type": "initialize", "handlers": [...], "provided_capabilities": [...]}
|
||||
2. Core -> Plugin: {"type": "result", "kind": "initialize_result", ...}
|
||||
3. Core -> Plugin: {"type": "invoke", "capability": "handler.invoke", ...}
|
||||
4. Plugin -> Core: {"type": "event", "phase": "started"}
|
||||
5. Plugin -> Core: {"type": "event", "phase": "delta", "data": {...}}
|
||||
6. Plugin -> Core: {"type": "event", "phase": "completed", "output": {...}}
|
||||
|
||||
插件加载对比:
|
||||
旧版 StarManager:
|
||||
- 通过 plugin.yaml 发现插件
|
||||
- 动态导入组件类并实例化
|
||||
- 注册到 star_handlers_registry
|
||||
- 使用 functools.partial 绑定实例
|
||||
|
||||
新版 loader.py:
|
||||
- PluginSpec 描述插件规范
|
||||
- PluginEnvironmentManager 管理虚拟环境
|
||||
- load_plugin() 加载并解析组件
|
||||
- LoadedHandler 封装处理器和描述符
|
||||
- 支持新旧 Star 组件兼容
|
||||
|
||||
传输层对比:
|
||||
旧版传输层:
|
||||
- 分离的 client/ 和 server/ 目录
|
||||
- JSONRPCClient 基类 + StdioClient/WebSocketClient
|
||||
- JSONRPCServer 基类 + StdioServer/WebSocketServer
|
||||
- 通过 set_message_handler 设置回调
|
||||
|
||||
新版传输层:
|
||||
- 统一的 Transport 抽象基类
|
||||
- StdioTransport: 支持进程模式和文件模式
|
||||
- WebSocketServerTransport: WebSocket 服务端
|
||||
- WebSocketClientTransport: WebSocket 客户端
|
||||
- 通过 set_message_handler 设置回调
|
||||
|
||||
处理器执行对比:
|
||||
旧版 HandlerExecutor:
|
||||
- 从 star_handlers_registry 获取处理器
|
||||
- 调用 handler(event, **args)
|
||||
- 通过 JSON-RPC notification 发送流式结果
|
||||
- 无参数注入支持
|
||||
|
||||
新版 HandlerDispatcher:
|
||||
- 从 LoadedHandler 映射获取处理器
|
||||
- 支持类型注解注入 (MessageEvent, Context)
|
||||
- 支持参数名注入 (event, ctx, context)
|
||||
- 支持 legacy_args 注入 (命令参数等)
|
||||
- 支持 Optional[Type] 类型
|
||||
- 统一的错误处理和生命周期回调
|
||||
|
||||
能力系统对比:
|
||||
旧版:
|
||||
- 无显式的能力声明系统
|
||||
- 通过 call_context_function 调用核心功能
|
||||
- 上下文函数硬编码在核心侧
|
||||
|
||||
新版 CapabilityRouter:
|
||||
- CapabilityDescriptor 声明能力
|
||||
- JSON Schema 验证输入输出
|
||||
- 支持流式能力 (stream_handler)
|
||||
- 内置能力:llm.chat, memory.*, db.*, platform.*
|
||||
- 支持 Supervisor 聚合并转发插件自定义 capability
|
||||
|
||||
`runtime` 负责把协议、传输、插件加载和生命周期管理拼成一条完整执行链:
|
||||
|
||||
- `Transport`: 只负责字符串级别收发
|
||||
- `Peer`: 负责协议消息、请求关联、流式事件和取消
|
||||
- `CapabilityRouter`: 核心侧能力注册与路由
|
||||
- `HandlerDispatcher`: 插件侧 handler 调用适配
|
||||
- `loader` / `bootstrap`: 插件发现、Worker 启动和 Supervisor 编排
|
||||
|
||||
设计上,legacy 兼容只出现在加载与分发边界;`Transport` 和 `Peer` 不直接携带
|
||||
旧版业务语义。
|
||||
这里仅暴露相对稳定的运行时构件:协议 `Peer`、传输抽象以及能力/处理器分发器。
|
||||
大多数插件作者应优先使用顶层 `astrbot_sdk` 或 `astrbot_sdk.api`。
|
||||
`loader` / `bootstrap` 等编排细节保留在各自子模块中,不作为根级稳定契约。
|
||||
"""
|
||||
|
||||
from .bootstrap import (
|
||||
PluginWorkerRuntime,
|
||||
SupervisorRuntime,
|
||||
WorkerSession,
|
||||
run_plugin_worker,
|
||||
run_supervisor,
|
||||
run_websocket_server,
|
||||
)
|
||||
from .capability_router import CapabilityRouter, StreamExecution
|
||||
from .handler_dispatcher import HandlerDispatcher
|
||||
from .loader import (
|
||||
LoadedCapability,
|
||||
LoadedHandler,
|
||||
LoadedPlugin,
|
||||
PluginDiscoveryResult,
|
||||
PluginEnvironmentManager,
|
||||
PluginSpec,
|
||||
discover_plugins,
|
||||
load_plugin,
|
||||
load_plugin_spec,
|
||||
)
|
||||
from .peer import (
|
||||
CancelHandler,
|
||||
InitializeHandler,
|
||||
InvokeHandler,
|
||||
Peer,
|
||||
)
|
||||
from .peer import Peer
|
||||
from .transport import (
|
||||
MessageHandler,
|
||||
StdioTransport,
|
||||
@@ -159,31 +17,13 @@ from .transport import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"CancelHandler",
|
||||
"CapabilityRouter",
|
||||
"HandlerDispatcher",
|
||||
"InitializeHandler",
|
||||
"InvokeHandler",
|
||||
"LoadedCapability",
|
||||
"LoadedHandler",
|
||||
"LoadedPlugin",
|
||||
"MessageHandler",
|
||||
"Peer",
|
||||
"PluginDiscoveryResult",
|
||||
"PluginEnvironmentManager",
|
||||
"PluginSpec",
|
||||
"PluginWorkerRuntime",
|
||||
"StdioTransport",
|
||||
"StreamExecution",
|
||||
"SupervisorRuntime",
|
||||
"Transport",
|
||||
"WebSocketClientTransport",
|
||||
"WebSocketServerTransport",
|
||||
"WorkerSession",
|
||||
"discover_plugins",
|
||||
"load_plugin",
|
||||
"load_plugin_spec",
|
||||
"run_plugin_worker",
|
||||
"run_supervisor",
|
||||
"run_websocket_server",
|
||||
]
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
from astrbot_sdk.api.components.command import CommandComponent
|
||||
from astrbot_sdk.api.event import AstrMessageEvent, filter
|
||||
from astrbot_sdk.api.event.filter import EventMessageType, PlatformAdapterType
|
||||
from astrbot_sdk.api.star.context import Context
|
||||
from astrbot_sdk.api.message import MessageChain
|
||||
from astrbot_sdk.api.message_components import Plain, At, Image
|
||||
@@ -77,15 +78,16 @@ class HelloCommand(CommandComponent):
|
||||
at = At(user_id="123", user_name="test_user")
|
||||
|
||||
# 测试 Image
|
||||
img = Image.fromURL("https://example.com/img.png")
|
||||
image = Image.fromURL("https://example.com/img.png")
|
||||
|
||||
# 测试 to_dict
|
||||
plain_dict = plain.to_dict()
|
||||
at_dict = at.to_dict()
|
||||
image_dict = image.to_dict()
|
||||
|
||||
logger.info(f"Plain: {plain_dict}, At: {at_dict}")
|
||||
logger.info(f"Plain: {plain_dict}, At: {at_dict}, Image: {image_dict}")
|
||||
|
||||
yield event.plain_result(f"Components created: Plain, At, Image")
|
||||
yield event.plain_result("Components created: Plain, At, Image")
|
||||
|
||||
@filter.regex(r"^ping.*")
|
||||
async def ping_regex(self, event: AstrMessageEvent):
|
||||
@@ -97,12 +99,12 @@ class HelloCommand(CommandComponent):
|
||||
"""测试权限过滤 (应该被跳过,因为没有 require_admin)。"""
|
||||
yield event.plain_result("Admin command executed")
|
||||
|
||||
@filter.event_message_type(filter.EventMessageType.GROUP_MESSAGE)
|
||||
@filter.event_message_type(EventMessageType.GROUP_MESSAGE)
|
||||
async def group_only(self, event: AstrMessageEvent):
|
||||
"""测试消息类型过滤。"""
|
||||
yield event.plain_result("Group message received")
|
||||
|
||||
@filter.platform_adapter_type("aiocqhttp")
|
||||
@filter.platform_adapter_type(PlatformAdapterType.AIOCQHTTP)
|
||||
async def cqhttp_only(self, event: AstrMessageEvent):
|
||||
"""测试平台过滤。"""
|
||||
yield event.plain_result("CQHttp platform detected")
|
||||
|
||||
@@ -9,14 +9,12 @@ import sys
|
||||
import tempfile
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from astrbot_sdk.protocol.descriptors import CommandTrigger, MessageTrigger
|
||||
from astrbot_sdk.runtime.loader import (
|
||||
LoadedPlugin,
|
||||
load_plugin,
|
||||
load_plugin_spec,
|
||||
)
|
||||
@@ -186,8 +184,9 @@ class TestFilterDecorators:
|
||||
def test_event_message_type_decorator(self):
|
||||
"""测试 event_message_type 装饰器。"""
|
||||
from astrbot_sdk.api.event import filter
|
||||
from astrbot_sdk.api.event.filter import EventMessageType
|
||||
|
||||
@filter.event_message_type(filter.EventMessageType.GROUP_MESSAGE)
|
||||
@filter.event_message_type(EventMessageType.GROUP_MESSAGE)
|
||||
@filter.command("group_cmd")
|
||||
async def handler(event):
|
||||
pass
|
||||
@@ -323,11 +322,12 @@ class TestLoadLegacyStylePlugin:
|
||||
manifest_path = plugin_dir / "plugin.yaml"
|
||||
requirements_path = plugin_dir / "requirements.txt"
|
||||
|
||||
commands_dir = plugin_dir / "commands"
|
||||
commands_dir.mkdir()
|
||||
(commands_dir / "__init__.py").write_text("", encoding="utf-8")
|
||||
# 使用唯一的模块名避免与其他测试冲突
|
||||
handlers_dir = plugin_dir / "chain_handlers"
|
||||
handlers_dir.mkdir()
|
||||
(handlers_dir / "__init__.py").write_text("", encoding="utf-8")
|
||||
|
||||
(commands_dir / "chain.py").write_text(
|
||||
(handlers_dir / "chain_cmd.py").write_text(
|
||||
textwrap.dedent("""
|
||||
from astrbot_sdk.api.components.command import CommandComponent
|
||||
from astrbot_sdk.api.event import AstrMessageEvent, filter
|
||||
@@ -355,7 +355,7 @@ class TestLoadLegacyStylePlugin:
|
||||
"runtime": {"python": "3.12"},
|
||||
"components": [
|
||||
{
|
||||
"class": "commands.chain:ChainCommand",
|
||||
"class": "chain_handlers.chain_cmd:ChainCommand",
|
||||
"type": "command",
|
||||
"name": "chain",
|
||||
}
|
||||
@@ -368,8 +368,10 @@ class TestLoadLegacyStylePlugin:
|
||||
|
||||
spec = load_plugin_spec(plugin_dir)
|
||||
|
||||
path_added = False
|
||||
if str(plugin_dir) not in sys.path:
|
||||
sys.path.insert(0, str(plugin_dir))
|
||||
path_added = True
|
||||
|
||||
try:
|
||||
loaded = load_plugin(spec)
|
||||
@@ -377,22 +379,30 @@ class TestLoadLegacyStylePlugin:
|
||||
assert len(loaded.instances) == 1
|
||||
assert len(loaded.handlers) >= 1
|
||||
finally:
|
||||
if str(plugin_dir) in sys.path:
|
||||
# 清理导入的模块
|
||||
modules_to_remove = [
|
||||
k for k in list(sys.modules.keys()) if k.startswith("chain_handlers")
|
||||
]
|
||||
for mod in modules_to_remove:
|
||||
del sys.modules[mod]
|
||||
if path_added and str(plugin_dir) in sys.path:
|
||||
sys.path.remove(str(plugin_dir))
|
||||
|
||||
def test_load_plugin_with_regex_handler(self):
|
||||
"""测试加载使用正则处理器的插件。"""
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
plugin_dir = Path(temp_dir) / "regex_plugin"
|
||||
plugin_dir.mkdir()
|
||||
manifest_path = plugin_dir / "plugin.yaml"
|
||||
requirements_path = plugin_dir / "requirements.txt"
|
||||
|
||||
commands_dir = plugin_dir / "commands"
|
||||
commands_dir.mkdir()
|
||||
(commands_dir / "__init__.py").write_text("", encoding="utf-8")
|
||||
# 使用唯一的模块名避免与其他测试冲突
|
||||
regex_handlers_dir = plugin_dir / "regex_handlers"
|
||||
regex_handlers_dir.mkdir()
|
||||
(regex_handlers_dir / "__init__.py").write_text("", encoding="utf-8")
|
||||
|
||||
(commands_dir / "matchers.py").write_text(
|
||||
(regex_handlers_dir / "matcher.py").write_text(
|
||||
textwrap.dedent("""
|
||||
from astrbot_sdk.api.components.command import CommandComponent
|
||||
from astrbot_sdk.api.event import AstrMessageEvent, filter
|
||||
@@ -416,7 +426,7 @@ class TestLoadLegacyStylePlugin:
|
||||
"runtime": {"python": "3.12"},
|
||||
"components": [
|
||||
{
|
||||
"class": "commands.matchers:RegexCommand",
|
||||
"class": "regex_handlers.matcher:RegexCommand",
|
||||
"type": "command",
|
||||
"name": "regex",
|
||||
}
|
||||
@@ -429,8 +439,10 @@ class TestLoadLegacyStylePlugin:
|
||||
|
||||
spec = load_plugin_spec(plugin_dir)
|
||||
|
||||
path_added = False
|
||||
if str(plugin_dir) not in sys.path:
|
||||
sys.path.insert(0, str(plugin_dir))
|
||||
path_added = True
|
||||
|
||||
try:
|
||||
loaded = load_plugin(spec)
|
||||
@@ -440,7 +452,13 @@ class TestLoadLegacyStylePlugin:
|
||||
assert isinstance(handler.descriptor.trigger, MessageTrigger)
|
||||
assert handler.descriptor.trigger.regex == r"^ping.*"
|
||||
finally:
|
||||
if str(plugin_dir) in sys.path:
|
||||
# 清理导入的模块
|
||||
modules_to_remove = [
|
||||
k for k in sys.modules if k.startswith("regex_handlers")
|
||||
]
|
||||
for mod in modules_to_remove:
|
||||
del sys.modules[mod]
|
||||
if path_added and str(plugin_dir) in sys.path:
|
||||
sys.path.remove(str(plugin_dir))
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import astrbot_sdk
|
||||
import astrbot_sdk.compat as compat_module
|
||||
import astrbot_sdk.runtime as runtime_module
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
@@ -25,6 +26,16 @@ from astrbot_sdk.context import Context
|
||||
from astrbot_sdk.decorators import on_command
|
||||
from astrbot_sdk.errors import AstrBotError, ErrorCodes
|
||||
from astrbot_sdk.events import MessageEvent
|
||||
from astrbot_sdk.runtime.capability_router import CapabilityRouter, StreamExecution
|
||||
from astrbot_sdk.runtime.handler_dispatcher import HandlerDispatcher
|
||||
from astrbot_sdk.runtime.peer import Peer
|
||||
from astrbot_sdk.runtime.transport import (
|
||||
MessageHandler,
|
||||
StdioTransport,
|
||||
Transport,
|
||||
WebSocketClientTransport,
|
||||
WebSocketServerTransport,
|
||||
)
|
||||
from astrbot_sdk.star import Star
|
||||
|
||||
TOP_LEVEL_MODULES = [
|
||||
@@ -36,6 +47,7 @@ TOP_LEVEL_MODULES = [
|
||||
"astrbot_sdk.decorators",
|
||||
"astrbot_sdk.errors",
|
||||
"astrbot_sdk.events",
|
||||
"astrbot_sdk.runtime",
|
||||
"astrbot_sdk.star",
|
||||
]
|
||||
|
||||
@@ -89,6 +101,41 @@ class TestTopLevelImports:
|
||||
"LegacyConversationManager",
|
||||
]
|
||||
|
||||
def test_runtime_module_reexports_advanced_runtime_primitives(self):
|
||||
"""runtime module should expose only the small advanced runtime surface."""
|
||||
assert runtime_module.Peer is Peer
|
||||
assert runtime_module.CapabilityRouter is CapabilityRouter
|
||||
assert runtime_module.HandlerDispatcher is HandlerDispatcher
|
||||
assert runtime_module.Transport is Transport
|
||||
assert runtime_module.MessageHandler is MessageHandler
|
||||
assert runtime_module.StdioTransport is StdioTransport
|
||||
assert runtime_module.WebSocketClientTransport is WebSocketClientTransport
|
||||
assert runtime_module.WebSocketServerTransport is WebSocketServerTransport
|
||||
assert runtime_module.StreamExecution is StreamExecution
|
||||
|
||||
def test_runtime_module_does_not_reexport_loader_or_bootstrap_details(self):
|
||||
"""runtime root should not expose loader/bootstrap internals as stable API."""
|
||||
assert not hasattr(runtime_module, "PluginEnvironmentManager")
|
||||
assert not hasattr(runtime_module, "PluginWorkerRuntime")
|
||||
assert not hasattr(runtime_module, "SupervisorRuntime")
|
||||
assert not hasattr(runtime_module, "WorkerSession")
|
||||
assert not hasattr(runtime_module, "LoadedPlugin")
|
||||
assert not hasattr(runtime_module, "run_supervisor")
|
||||
|
||||
def test_runtime_module_all_matches_narrow_public_surface(self):
|
||||
"""runtime.__all__ should stay aligned with the narrowed advanced API."""
|
||||
assert runtime_module.__all__ == [
|
||||
"CapabilityRouter",
|
||||
"HandlerDispatcher",
|
||||
"MessageHandler",
|
||||
"Peer",
|
||||
"StdioTransport",
|
||||
"StreamExecution",
|
||||
"Transport",
|
||||
"WebSocketClientTransport",
|
||||
"WebSocketServerTransport",
|
||||
]
|
||||
|
||||
|
||||
class TestCliModule:
|
||||
"""Tests for cli.py and __main__.py."""
|
||||
|
||||
Reference in New Issue
Block a user