refactor: remove unused hello command, echo listener, and related tools; add CLI and context enhancements

This commit is contained in:
Soulter
2025-11-11 17:22:03 +08:00
parent e982e7798d
commit 67cc4c5715
17 changed files with 111 additions and 101 deletions

View File

@@ -1,12 +0,0 @@
from astrbot.api.v1.components.command import CommandComponent
from astrbot.api.v1.event import AstrMessageEvent, filter
from astrbot.api.v1.context import Context
class HelloCommand(CommandComponent):
def __init__(self, context: Context):
super().__init__(context)
@filter.command("hello")
async def hello(self, event: AstrMessageEvent):
yield event.plain_result("Hello, Astrbot!")

View File

@@ -1,12 +0,0 @@
from astrbot.api.v1.components.listener import ListenerComponent
from astrbot.api.v1.event import AstrMessageEvent, filter
from astrbot.api.v1.context import Context
class EchoListener(ListenerComponent):
def __init__(self, context: Context):
super().__init__(context)
@filter.platform_adapter_type(filter.PlatformAdapterType.ALL)
async def on_message(self, event: AstrMessageEvent):
yield event.plain_result("Hello, Astrbot!")

View File

@@ -1,31 +0,0 @@
from mcp.types import CallToolResult
from pydantic import Field
from pydantic.dataclasses import dataclass
from astrbot.api.v1.components.agent.tool import FunctionTool
from astrbot.api.v1.components.agent import ContextWrapper, AstrAgentContext
@dataclass
class HelloWorldTool(FunctionTool):
name: str = "hello_world" # 工具名称
description: str = "Say hello to the world." # 工具描述
parameters: dict = Field(
default_factory=lambda: {
"type": "object",
"properties": {
"greeting": {
"type": "string",
"description": "The greeting message.",
},
},
"required": ["greeting"],
}
) # 工具参数定义,见 OpenAI 官网或 https://json-schema.org/understanding-json-schema/
async def call(
self, context: ContextWrapper[AstrAgentContext], **kwargs
) -> str | CallToolResult:
# event 在 context.context.event 中可用
greeting = kwargs.get("greeting", "Hello")
return f"{greeting}, World!" # 也支持 mcp.types.CallToolResult 类型

View File

@@ -1,22 +0,0 @@
_schema_version: 2
name: astrbot_plugin_helloworld
display_name: HelloWorld 插件
desc: 一个简单的问候插件示例
author: Soulter
version: 0.1.0
components: # 组件列表,将支持自动生成
- class: plugin_sample.commands.hello:HelloCommand
type: command
name: hello
description: 发送问候消息
subcommands:
- name: wow
description: 发送 "Hello, Astrbot!" 消息
- class: plugin_sample.tools.echo:EchoTool
type: llm_tool
name: echo_tool
description: 回显输入的消息
- class: plugin_sample.listeners.message_listener:MessageListener
type: listener
name: message_logger
description: 监听并记录所有消息

View File

@@ -7,8 +7,12 @@ requires-python = ">=3.12"
dependencies = [
"aiohttp>=3.13.2",
"certifi>=2025.10.5",
"click>=8.3.0",
"docstring-parser>=0.17.0",
"loguru>=0.7.3",
"pydantic>=2.12.3",
"pyyaml>=6.0.3",
]
[project.scripts]
astr = "astrbot_sdk.cli:cli"

View File

@@ -0,0 +1,3 @@
from .main import cli
__all__ = ['cli']

View File

@@ -0,0 +1,50 @@
import asyncio
import sys
import click
from loguru import logger
from ..runtime.start_server import amain as run_server
def setup_logger(verbose: bool = False):
"""Configure loguru for CLI output"""
# Remove default handler
logger.remove()
# Add custom handler with CLI-friendly format
log_format = (
"<green>{time:HH:mm:ss}</green> | "
"<level>{level: <8}</level> | "
"<level>{message}</level>"
)
level = "DEBUG" if verbose else "INFO"
logger.add(
sys.stderr,
format=log_format,
level=level,
colorize=True,
)
@click.group()
@click.option("-v", "--verbose", is_flag=True, help="Enable verbose output")
@click.pass_context
def cli(ctx, verbose):
"""AstrBot SDK CLI"""
ctx.ensure_object(dict)
ctx.obj["verbose"] = verbose
setup_logger(verbose)
@cli.command()
@click.option("--port", default=8765, help="WebSocket server port", type=int)
@click.pass_context
def run(ctx, port: int):
"""Start the WebSocket server"""
logger.info(f"Starting WebSocket server on port {port}...")
asyncio.run(run_server(port))
if __name__ == "__main__":
cli()

View File

@@ -6,5 +6,13 @@ class Context(BaseContext):
def __init__(self, conversation_manager: ConversationManager):
self.conversation_manager = conversation_manager
def _inject_rpc_handlers(self, runner):
setattr(self.conversation_manager, "runner", runner)
@classmethod
def default_context(cls, runner=None):
"""Create a default context instance.
Args:
runner: Optional StarRunner instance to inject into conversation manager.
If provided, enables RPC functionality.
"""
conversation_manager = ConversationManager(runner=runner)
return cls(conversation_manager=conversation_manager)

View File

@@ -3,6 +3,14 @@ from .util import rpc_method
class ConversationManager(BaseConversationManager):
def __init__(self, runner=None):
"""Initialize ConversationManager.
Args:
runner: Optional StarRunner instance for RPC functionality.
"""
self.runner = runner
@rpc_method
async def new_conversation(
self,

View File

@@ -1,6 +1,7 @@
import yaml
import importlib
import functools
import sys
from pathlib import Path
from loguru import logger
from .stars.registry import star_handlers_registry, star_map, star_registry
@@ -20,13 +21,21 @@ class StarManager:
root_dir (Path | None): The root directory to search for plugin.yaml. Defaults to None, which means the current working directory.
"""
if root_dir is None:
root_dir = Path.cwd().relative_to(Path.cwd())
root_dir = Path.cwd()
else:
root_dir = Path.cwd().joinpath(root_dir).resolve()
root_dir = Path(root_dir).resolve()
path = root_dir / "plugin.yaml"
if not path.exists():
logger.warning("No plugin.yaml found in the current directory.")
return []
# Add the plugin directory to sys.path so we can import its modules
root_dir_str = str(root_dir)
if root_dir_str not in sys.path:
sys.path.insert(0, root_dir_str)
logger.debug(f"Added {root_dir_str} to sys.path")
with open(path, "r", encoding="utf-8") as f:
data = yaml.safe_load(f)
@@ -46,7 +55,7 @@ class StarManager:
full_name_list = []
for comp in components:
class_ = comp.get("class", "")
print(f"Loading component: {class_}")
logger.debug(f"Loading component: {class_}")
if not class_:
logger.warning(f"Component without class found: {comp}")
continue
@@ -56,10 +65,7 @@ class StarManager:
continue
# dynamically register the component
try:
# we need edit the module path to be relative to the root_dir
root_dir_dot = str(root_dir).replace("/", ".").lstrip(".")
if root_dir_dot:
module_path = f"{root_dir_dot}.{module_path}"
logger.debug(f"Importing module: {module_path}")
module_type = importlib.import_module(module_path)
logger.info(f"Successfully loaded component module: {module_path}")
component_cls = getattr(module_type, class_name)

View File

@@ -10,8 +10,7 @@ from .rpc.jsonrpc import (
JSONRPCErrorResponse,
JSONRPCErrorData,
)
from .types import CallHandlerRequest, HandshakeRequest
from ..api.event.astr_message_event import AstrMessageEvent
from .types import CallHandlerRequest
class StarRunner:

View File

@@ -4,31 +4,26 @@ from .rpc.server import WebSocketServer
from .star_runner import StarRunner
from .star_manager import StarManager
from ..runtime.api.context import Context
from ..runtime.api.conversation_mgr import ConversationManager
from loguru import logger
async def amain():
server = WebSocketServer(port=8765)
conversation_manager = ConversationManager()
context = Context(conversation_manager=conversation_manager)
async def amain(port: int = 8765):
server = WebSocketServer(port=port)
runner = StarRunner(server)
context._inject_rpc_handlers(runner=runner)
context = Context.default_context(runner=runner)
star_manager = StarManager(context=context)
star_manager.discover_star()
await runner.run()
# 设置停止事件
stop_event = asyncio.Event()
# 注册信号处理器
loop = asyncio.get_running_loop()
for sig in (signal.SIGTERM, signal.SIGINT):
loop.add_signal_handler(sig, stop_event.set)
print("Server is running. Press Ctrl+C to stop.")
logger.info("Server is running. Press Ctrl+C to stop.")
try:
await stop_event.wait()
finally:
print("Shutting down...")
logger.info("Shutting down...")
await server.stop()

View File

@@ -3,4 +3,4 @@ from astrbot_sdk.runtime.start_server import amain
import asyncio
if __name__ == "__main__":
asyncio.run(amain())
asyncio.run(amain()) # 使用默认端口 8765

View File

@@ -5,7 +5,7 @@ desc: 一个简单的问候插件示例
author: Soulter
version: 0.1.0
components: # 组件列表,将支持自动生成
- class: test_plugin.commands.hello:HelloCommand
- class: commands.hello:HelloCommand
type: command
name: hello
description: 发送问候消息

14
uv.lock generated
View File

@@ -125,6 +125,7 @@ source = { virtual = "." }
dependencies = [
{ name = "aiohttp" },
{ name = "certifi" },
{ name = "click" },
{ name = "docstring-parser" },
{ name = "loguru" },
{ name = "pydantic" },
@@ -135,6 +136,7 @@ dependencies = [
requires-dist = [
{ name = "aiohttp", specifier = ">=3.13.2" },
{ name = "certifi", specifier = ">=2025.10.5" },
{ name = "click", specifier = ">=8.3.0" },
{ name = "docstring-parser", specifier = ">=0.17.0" },
{ name = "loguru", specifier = ">=0.7.3" },
{ name = "pydantic", specifier = ">=2.12.3" },
@@ -159,6 +161,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286 },
]
[[package]]
name = "click"
version = "8.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295 },
]
[[package]]
name = "colorama"
version = "0.4.6"