Plugin Register & API Expose
Every plugin must declare metadata and a plugin class. This section explains how to register a plugin, define its metadata, and expose APIs (HTTP, streaming, or function calls).
1) Plugin Metadata (__plugin_meta__)
Each plugin must define __plugin_meta__ = PluginMetadata(...). Fill it carefully and accurately—this information is used for discovery, dependency resolution, routing, and UI/display.
Guidelines for fields:
name(required): A short, unique, human-readable plugin name.version(required): Follow semantic versioning (e.g.,1.2.0).description(required): What the plugin does and when to use it.author(required): Owner/maintainer identity (team/company).url(required): Project/repo/docs link for the plugin.required_remote_apis: A list of external API paths your plugin depends on (e.g. endpoints provided by other plugins/services). It enables dependency checks and optional preflight validation.
For example:
__plugin_meta__ = PluginMetadata(
name="echo",
version=VERSION,
description="原神会重复你说的话",
author="原神",
url="https://github.com/touale/FrameX-kit",
required_remote_apis=[],
)
2) Plugin Class & Registration
Each plugin must implement a class decorated with @on_register() and inherit from BasePlugin.
For example:
@on_register()
class EchoPlugin(BasePlugin):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
# Initialize lightweight resources here (e.g., clients, caches)
async def on_start(self) -> None:
# Initialize heavy/async resources here (e.g., DB connections, pools)
# Called by the runtime when the plugin is starting.
...
Notes:
- Use
__init__for fast, synchronous setup. (Initialization of parameter values) - Use
on_startfor asynchronous or heavy initialization (DB, message queues, engines).
3) Exposing APIs with @on_request(...)
Annotate methods on your plugin class with @on_request(...) to expose them as callable endpoints.
Parameters:
path:- For
ApiType.HTTP: the URL path (e.g."/echo"or"/v1/echo"). - For
ApiType.FUNC: the symbolic name of the function. If omitted, the method name is used.
- For
methods: HTTP methods (e.g.["GET"],["POST"]). For function calls, leave it asNone.call_type: One ofApiType.HTTPorApiType.FUNC.stream: Whether the endpoint is streaming. IfTrue, the handler should be an async generator or follow the framework’s streaming contract.
ApiType Explained
-
ApiType.HTTP- Exposes the plugin method as an HTTP endpoint.
- Can be consumed by external clients (e.g., web backends, mobile apps, or third-party services).
- Can also be used for plugin-to-plugin communication.
- Offers broader accessibility and is automatically documented under
/docs(Swagger/OpenAPI). - Recommended when you want to share functionality beyond the plugin boundary.
-
ApiType.FUNC- Exposes the plugin method as an internal remote function.
- Only callable from within the FrameX plugin ecosystem.
- Provides stricter scoping and reduced exposure surface.
- Recommended for internal-only calls, utility helpers, or private APIs that should not be published externally.
3.1 HTTP example (non-stream)
__plugin_meta__ = PluginMetadata(
name="echo",
version=VERSION,
description="原神会重复你说的话",
author="原神",
url="https://github.com/touale/FrameX-kit",
required_remote_apis=[],
)
class EchoModel(BaseModel):
id: int
name: str = "原神"
@on_register()
class EchoPlugin(BasePlugin):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
@on_request("/echo", methods=["GET"])
async def echo(self, message: str) -> str:
return message
3.2 HTTP example (streaming)
...
@on_register()
class EchoPlugin(BasePlugin):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
@on_request(path="/echo_stream", methods=["GET"], call_type=ApiType.HTTP, stream=True)
async def echo_stream(self, message: str):
# yield chunked events/messages
for ch in f"Streaming: {message}":
yield {"type": "chunk", "data": ch}
yield {"type": "finish"}
3.3 Function call example
...
@on_register()
class EchoPlugin(BasePlugin):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
@on_request(call_type=ApiType.FUNC)
async def add(self, a: int, b: int) -> int:
return a + b
4) End-to-End Example
# src/__init__.py
import asyncio
from collections.abc import AsyncGenerator
from typing import Any
from pydantic import BaseModel
from framex.consts import VERSION
from framex.plugin import BasePlugin, PluginMetadata, on_register, on_request
from framex.plugin.model import ApiType
from framex.utils import StreamEnventType, make_stream_event
__plugin_meta__ = PluginMetadata(
name="echo",
version=VERSION,
description="原神会重复你说的话",
author="原神",
url="https://github.com/touale/FrameX-kit",
required_remote_apis=[],
)
class EchoModel(BaseModel):
id: int
name: str = "原神"
@on_register()
class EchoPlugin(BasePlugin):
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
@on_request("/echo", methods=["GET"])
async def echo(self, message: str) -> str:
return message
@on_request("/echo_model", methods=["POST"])
async def echo_model(self, message: str, model: EchoModel) -> str:
return f"{message},{model.model_dump()}"
@on_request("/api/v1/echo_stream", methods=["GET"], stream=True)
async def echo_stream(self, message: str) -> AsyncGenerator[str, None]:
for char in f"原神真好玩呀, {message}":
yield make_stream_event(StreamEnventType.MESSAGE_CHUNK, char)
await asyncio.sleep(0.1)
yield make_stream_event(StreamEnventType.FINISH)
@on_request(call_type=ApiType.FUNC)
async def confess(self, message: str) -> str:
return f"我是原神哟! 收到您的消息{message}"