langbot-plugin 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- langbot_plugin/__init__.py +0 -0
- langbot_plugin/api/__init__.py +0 -0
- langbot_plugin/cli/__init__.py +35 -0
- langbot_plugin/cli/command.py +9 -0
- langbot_plugin/cli/commands/__init__.py +0 -0
- langbot_plugin/entities/__init__.py +0 -0
- langbot_plugin/entities/io/__init__.py +0 -0
- langbot_plugin/entities/io/errors.py +31 -0
- langbot_plugin/entities/io/req.py +16 -0
- langbot_plugin/entities/io/resp.py +19 -0
- langbot_plugin/runtime/__init__.py +0 -0
- langbot_plugin/runtime/__main__.py +10 -0
- langbot_plugin/runtime/app.py +60 -0
- langbot_plugin/runtime/controller/__init__.py +0 -0
- langbot_plugin/runtime/controller/stdio/__init__.py +0 -0
- langbot_plugin/runtime/controller/stdio/server.py +18 -0
- langbot_plugin/runtime/controller/ws/__init__.py +0 -0
- langbot_plugin/runtime/controller/ws/server.py +74 -0
- langbot_plugin/runtime/core/__init__.py +0 -0
- langbot_plugin/runtime/io/connection.py +19 -0
- langbot_plugin/runtime/io/connections/__init__.py +0 -0
- langbot_plugin/runtime/io/connections/stdio.py +24 -0
- langbot_plugin/runtime/io/connections/ws.py +26 -0
- langbot_plugin/runtime/io/handler.py +129 -0
- langbot_plugin/runtime/io/handlers/__init__.py +0 -0
- langbot_plugin/runtime/io/handlers/control.py +23 -0
- langbot_plugin/runtime/service/__init__.py +0 -0
- langbot_plugin/utils/__init__.py +0 -0
- langbot_plugin/utils/importutil.py +38 -0
- langbot_plugin/version.py +1 -0
- langbot_plugin-0.1.0.dist-info/METADATA +30 -0
- langbot_plugin-0.1.0.dist-info/RECORD +34 -0
- langbot_plugin-0.1.0.dist-info/WHEEL +4 -0
- langbot_plugin-0.1.0.dist-info/entry_points.txt +2 -0
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from langbot_plugin.version import __version__
|
|
3
|
+
from langbot_plugin.runtime import __main__ as runtime_main
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
parser = argparse.ArgumentParser(description="LangBot Plugin CLI")
|
|
8
|
+
|
|
9
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
10
|
+
|
|
11
|
+
version_parser = subparsers.add_parser("ver", help="Show the version of the CLI")
|
|
12
|
+
|
|
13
|
+
init_parser = subparsers.add_parser("init", help="Initialize a new plugin")
|
|
14
|
+
init_parser.add_argument(
|
|
15
|
+
"--name", "-n", action="store", type=str, help="The name of the plugin"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
runtime_parser = subparsers.add_parser("rt", help="Run the runtime")
|
|
19
|
+
runtime_parser.add_argument(
|
|
20
|
+
"--stdio-control", "-s", action="store_true", default=False
|
|
21
|
+
)
|
|
22
|
+
runtime_parser.add_argument("--ws-control-port", type=int, default=5400)
|
|
23
|
+
runtime_parser.add_argument("--ws-debug-port", type=int, default=5401)
|
|
24
|
+
|
|
25
|
+
args = parser.parse_args()
|
|
26
|
+
|
|
27
|
+
match args.command:
|
|
28
|
+
case "ver":
|
|
29
|
+
print(f"LangBot Plugin CLI v{__version__}")
|
|
30
|
+
case "init":
|
|
31
|
+
print(f"Initializing plugin {args.name}")
|
|
32
|
+
case "rt":
|
|
33
|
+
runtime_main.main(args)
|
|
34
|
+
case _:
|
|
35
|
+
print(f"Unknown command: {args.command}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ConnectionClosedError(Exception):
|
|
5
|
+
"""The connection is closed."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, message: str):
|
|
8
|
+
self.message = message
|
|
9
|
+
|
|
10
|
+
def __str__(self):
|
|
11
|
+
return self.message
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ActionCallTimeoutError(Exception):
|
|
15
|
+
"""The action call timed out."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, message: str):
|
|
18
|
+
self.message = message
|
|
19
|
+
|
|
20
|
+
def __str__(self):
|
|
21
|
+
return self.message
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ActionCallError(Exception):
|
|
25
|
+
"""The action call failed."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, message: str):
|
|
28
|
+
self.message = message
|
|
29
|
+
|
|
30
|
+
def __str__(self):
|
|
31
|
+
return self.message
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pydantic
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ActionRequest(pydantic.BaseModel):
|
|
8
|
+
seq_id: int = pydantic.Field(..., description="The sequence id of the request")
|
|
9
|
+
action: str
|
|
10
|
+
data: dict[str, Any]
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def make_request(
|
|
14
|
+
cls, seq_id: int, action: str, data: dict[str, Any]
|
|
15
|
+
) -> ActionRequest:
|
|
16
|
+
return cls(seq_id=seq_id, action=action, data=data)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pydantic
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ActionResponse(pydantic.BaseModel):
|
|
8
|
+
seq_id: Optional[int] = None
|
|
9
|
+
code: int = pydantic.Field(..., description="The code of the response")
|
|
10
|
+
message: str = pydantic.Field(..., description="The message of the response")
|
|
11
|
+
data: dict[str, Any] = pydantic.Field(..., description="The data of the response")
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def success(cls, data: dict[str, Any]) -> ActionResponse:
|
|
15
|
+
return cls(seq_id=0, code=0, message="success", data=data)
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def error(cls, message: str) -> ActionResponse:
|
|
19
|
+
return cls(code=1, message=message, data={})
|
|
File without changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
|
|
8
|
+
from langbot_plugin.runtime.controller.stdio import server as stdio_controller_server
|
|
9
|
+
from langbot_plugin.runtime.controller.ws import server as ws_controller_server
|
|
10
|
+
from langbot_plugin.runtime.io import handler
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ControlConnectionMode(Enum):
|
|
14
|
+
STDIO = "stdio"
|
|
15
|
+
WS = "ws"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Application:
|
|
19
|
+
"""Runtime application context."""
|
|
20
|
+
|
|
21
|
+
handler_manager: handler.HandlerManager
|
|
22
|
+
|
|
23
|
+
control_connection_mode: ControlConnectionMode
|
|
24
|
+
|
|
25
|
+
stdio_server: stdio_controller_server.StdioServer | None = (
|
|
26
|
+
None # stdio control server
|
|
27
|
+
)
|
|
28
|
+
ws_server: ws_controller_server.WebSocketServer | None = (
|
|
29
|
+
None # ws control/debug server
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def __init__(self, args: argparse.Namespace):
|
|
33
|
+
self.args = args
|
|
34
|
+
self.handler_manager = handler.HandlerManager()
|
|
35
|
+
|
|
36
|
+
if args.stdio_control:
|
|
37
|
+
self.control_connection_mode = ControlConnectionMode.STDIO
|
|
38
|
+
else:
|
|
39
|
+
self.control_connection_mode = ControlConnectionMode.WS
|
|
40
|
+
|
|
41
|
+
# build controllers layer
|
|
42
|
+
if self.control_connection_mode == ControlConnectionMode.STDIO:
|
|
43
|
+
self.stdio_server = stdio_controller_server.StdioServer(
|
|
44
|
+
self.handler_manager
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
self.ws_server = ws_controller_server.WebSocketServer(
|
|
48
|
+
self.args.ws_control_port, self.args.ws_debug_port, self.handler_manager
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
async def run(self):
|
|
52
|
+
tasks = []
|
|
53
|
+
|
|
54
|
+
if self.stdio_server:
|
|
55
|
+
tasks.append(self.stdio_server.run())
|
|
56
|
+
|
|
57
|
+
if self.ws_server:
|
|
58
|
+
tasks.append(self.ws_server.run())
|
|
59
|
+
|
|
60
|
+
await asyncio.gather(*tasks)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Stdio server for LangBot control connection
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from langbot_plugin.runtime.io.connections import stdio as stdio_connection
|
|
5
|
+
from langbot_plugin.runtime.io.handlers import control as control_handler
|
|
6
|
+
from langbot_plugin.runtime.io import handler as io_handler
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StdioServer:
|
|
10
|
+
def __init__(self, handler_manager: io_handler.HandlerManager):
|
|
11
|
+
self.handler_manager = handler_manager
|
|
12
|
+
|
|
13
|
+
async def run(self):
|
|
14
|
+
print("Starting Stdio server")
|
|
15
|
+
connection = stdio_connection.StdioConnection()
|
|
16
|
+
handler = control_handler.ControlConnectionHandler(connection)
|
|
17
|
+
task = self.handler_manager.set_control_handler(handler)
|
|
18
|
+
await task
|
|
File without changes
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import websockets
|
|
5
|
+
|
|
6
|
+
from langbot_plugin.runtime.io.connections import ws as ws_connection
|
|
7
|
+
from langbot_plugin.runtime.io.handlers import control as control_handler
|
|
8
|
+
from langbot_plugin.runtime.io import handler as io_handler
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ControlConnectionWebSocketServer:
|
|
12
|
+
"""The server for control connection WebSocket connections."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, port: int, handler_manager: io_handler.HandlerManager):
|
|
15
|
+
self.port = port
|
|
16
|
+
self.handler_manager = handler_manager
|
|
17
|
+
|
|
18
|
+
async def run(self):
|
|
19
|
+
server = await websockets.serve(self.handle_connection, "0.0.0.0", self.port)
|
|
20
|
+
await server.wait_closed()
|
|
21
|
+
|
|
22
|
+
async def handle_connection(self, websocket: websockets.ServerConnection):
|
|
23
|
+
print(f"New control connection from {websocket.remote_address}")
|
|
24
|
+
connection = ws_connection.WebSocketConnection(websocket)
|
|
25
|
+
handler = control_handler.ControlConnectionHandler(connection)
|
|
26
|
+
task = self.handler_manager.set_control_handler(handler)
|
|
27
|
+
await task
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DebugConnectionWebSocketServer:
|
|
31
|
+
"""The server for debug connection WebSocket connections."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, port: int, handler_manager: io_handler.HandlerManager):
|
|
34
|
+
self.port = port
|
|
35
|
+
self.handler_manager = handler_manager
|
|
36
|
+
|
|
37
|
+
async def run(self):
|
|
38
|
+
server = await websockets.serve(self.handle_connection, "0.0.0.0", self.port)
|
|
39
|
+
await server.wait_closed()
|
|
40
|
+
|
|
41
|
+
async def handle_connection(self, websocket: websockets.ServerConnection):
|
|
42
|
+
print(f"New connection from {websocket.remote_address}")
|
|
43
|
+
await websocket.send("Hello, world!")
|
|
44
|
+
await websocket.close()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class WebSocketServer:
|
|
48
|
+
"""The server for control connection WebSocket connections."""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
control_port: int,
|
|
53
|
+
debug_port: int,
|
|
54
|
+
handler_manager: io_handler.HandlerManager,
|
|
55
|
+
):
|
|
56
|
+
self.control_port = control_port
|
|
57
|
+
self.debug_port = debug_port
|
|
58
|
+
self.handler_manager = handler_manager
|
|
59
|
+
|
|
60
|
+
async def run(self):
|
|
61
|
+
print(
|
|
62
|
+
f"Starting WebSocket server on port {self.control_port} for control connections"
|
|
63
|
+
)
|
|
64
|
+
print(
|
|
65
|
+
f"Starting WebSocket server on port {self.debug_port} for debug connections"
|
|
66
|
+
)
|
|
67
|
+
control_server = ControlConnectionWebSocketServer(
|
|
68
|
+
self.control_port, self.handler_manager
|
|
69
|
+
)
|
|
70
|
+
debug_server = DebugConnectionWebSocketServer(
|
|
71
|
+
self.debug_port, self.handler_manager
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
await asyncio.gather(control_server.run(), debug_server.run())
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Connection(abc.ABC):
|
|
7
|
+
"""The abstract base class for all connections."""
|
|
8
|
+
|
|
9
|
+
@abc.abstractmethod
|
|
10
|
+
async def send(self, message: str) -> None:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
@abc.abstractmethod
|
|
14
|
+
async def receive(self) -> str:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@abc.abstractmethod
|
|
18
|
+
async def close(self) -> None:
|
|
19
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from langbot_plugin.runtime.io import connection
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StdioConnection(connection.Connection):
|
|
9
|
+
"""The connection for Stdio connections."""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
async def send(self, message: str) -> None:
|
|
15
|
+
print(message)
|
|
16
|
+
|
|
17
|
+
async def receive(self) -> str:
|
|
18
|
+
while True:
|
|
19
|
+
s = await asyncio.to_thread(input)
|
|
20
|
+
if s.startswith("{") and s.endswith("}"):
|
|
21
|
+
return s
|
|
22
|
+
|
|
23
|
+
async def close(self) -> None:
|
|
24
|
+
pass
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import websockets
|
|
4
|
+
|
|
5
|
+
from langbot_plugin.runtime.io import connection as io_connection
|
|
6
|
+
from langbot_plugin.entities.io.errors import ConnectionClosedError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WebSocketConnection(io_connection.Connection):
|
|
10
|
+
"""The connection for WebSocket connections."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, websocket: websockets.ServerConnection):
|
|
13
|
+
self.websocket = websocket
|
|
14
|
+
|
|
15
|
+
async def send(self, message: str) -> None:
|
|
16
|
+
await self.websocket.send(message, text=True)
|
|
17
|
+
|
|
18
|
+
async def receive(self) -> str:
|
|
19
|
+
try:
|
|
20
|
+
data = await self.websocket.recv(decode=True)
|
|
21
|
+
return data
|
|
22
|
+
except websockets.exceptions.ConnectionClosed:
|
|
23
|
+
raise ConnectionClosedError("Connection closed")
|
|
24
|
+
|
|
25
|
+
async def close(self) -> None:
|
|
26
|
+
await self.websocket.close()
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
import asyncio
|
|
5
|
+
import json
|
|
6
|
+
from typing import Callable, Any, Dict, Awaitable, Coroutine
|
|
7
|
+
|
|
8
|
+
import pydantic
|
|
9
|
+
|
|
10
|
+
from langbot_plugin.runtime.io import connection
|
|
11
|
+
from langbot_plugin.entities.io.req import ActionRequest
|
|
12
|
+
from langbot_plugin.entities.io.resp import ActionResponse
|
|
13
|
+
from langbot_plugin.entities.io.errors import (
|
|
14
|
+
ConnectionClosedError,
|
|
15
|
+
ActionCallTimeoutError,
|
|
16
|
+
ActionCallError,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Handler(abc.ABC):
|
|
21
|
+
"""The abstract base class for all handlers."""
|
|
22
|
+
|
|
23
|
+
conn: connection.Connection
|
|
24
|
+
|
|
25
|
+
actions: dict[str, Callable[[dict[str, Any]], Coroutine[Any, Any, ActionResponse]]]
|
|
26
|
+
|
|
27
|
+
resp_waiters: dict[int, asyncio.Future[ActionResponse]] = {}
|
|
28
|
+
|
|
29
|
+
seq_id_index: int = 0
|
|
30
|
+
|
|
31
|
+
def __init__(self, connection: connection.Connection):
|
|
32
|
+
self.conn = connection
|
|
33
|
+
self.actions = {}
|
|
34
|
+
|
|
35
|
+
async def run(self) -> None:
|
|
36
|
+
while True:
|
|
37
|
+
message = None
|
|
38
|
+
try:
|
|
39
|
+
message = await self.conn.receive()
|
|
40
|
+
except ConnectionClosedError:
|
|
41
|
+
print("Connection closed")
|
|
42
|
+
break
|
|
43
|
+
if message is None:
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
async def handle_message(message: str):
|
|
47
|
+
# sh*t, i dont really know how to use generic type here
|
|
48
|
+
# so just use dict[str, Any] for now
|
|
49
|
+
req_data = json.loads(message)
|
|
50
|
+
seq_id = req_data["seq_id"] if "seq_id" in req_data else -1
|
|
51
|
+
|
|
52
|
+
if "action" in req_data: # action request from peer
|
|
53
|
+
try:
|
|
54
|
+
if req_data["action"] not in self.actions:
|
|
55
|
+
raise ValueError(f"Action {req_data['action']} not found")
|
|
56
|
+
|
|
57
|
+
response = await self.actions[req_data["action"]](
|
|
58
|
+
req_data["data"]
|
|
59
|
+
)
|
|
60
|
+
response.seq_id = seq_id
|
|
61
|
+
await self.conn.send(json.dumps(response.model_dump()))
|
|
62
|
+
except Exception as e:
|
|
63
|
+
error_response = ActionResponse.error(
|
|
64
|
+
f"{e.__class__.__name__}: {str(e)}"
|
|
65
|
+
)
|
|
66
|
+
error_response.seq_id = seq_id
|
|
67
|
+
await self.conn.send(json.dumps(error_response.model_dump()))
|
|
68
|
+
|
|
69
|
+
elif "code" in req_data: # action response from peer
|
|
70
|
+
if seq_id in self.resp_waiters:
|
|
71
|
+
response = ActionResponse.success(req_data["data"])
|
|
72
|
+
response.seq_id = seq_id
|
|
73
|
+
self.resp_waiters[seq_id].set_result(response)
|
|
74
|
+
del self.resp_waiters[seq_id]
|
|
75
|
+
|
|
76
|
+
asyncio.create_task(handle_message(message))
|
|
77
|
+
|
|
78
|
+
async def call_action(
|
|
79
|
+
self, action: str, data: dict[str, Any], timeout: float = 10.0
|
|
80
|
+
) -> dict[str, Any]:
|
|
81
|
+
"""Actively call an action provided by the peer, and wait for the response."""
|
|
82
|
+
self.seq_id_index += 1
|
|
83
|
+
request = ActionRequest.make_request(self.seq_id_index, action, data)
|
|
84
|
+
await self.conn.send(json.dumps(request.model_dump()))
|
|
85
|
+
# wait for response
|
|
86
|
+
future = asyncio.Future[ActionResponse]()
|
|
87
|
+
self.resp_waiters[self.seq_id_index] = future
|
|
88
|
+
try:
|
|
89
|
+
response = await asyncio.wait_for(future, timeout)
|
|
90
|
+
if response.code != 0:
|
|
91
|
+
raise ActionCallError(f"{response.message}")
|
|
92
|
+
return response.data
|
|
93
|
+
except asyncio.TimeoutError:
|
|
94
|
+
raise ActionCallTimeoutError(f"Action {action} call timed out")
|
|
95
|
+
except Exception as e:
|
|
96
|
+
raise ActionCallError(f"{e.__class__.__name__}: {str(e)}")
|
|
97
|
+
|
|
98
|
+
# decorator to register an action
|
|
99
|
+
def action(
|
|
100
|
+
self, name: str
|
|
101
|
+
) -> Callable[
|
|
102
|
+
[Callable[[dict[str, Any]], Coroutine[Any, Any, ActionResponse]]],
|
|
103
|
+
Callable[[dict[str, Any]], Coroutine[Any, Any, ActionResponse]],
|
|
104
|
+
]:
|
|
105
|
+
def decorator(
|
|
106
|
+
func: Callable[[dict[str, Any]], Coroutine[Any, Any, ActionResponse]],
|
|
107
|
+
) -> Callable[[dict[str, Any]], Coroutine[Any, Any, ActionResponse]]:
|
|
108
|
+
self.actions[name] = func
|
|
109
|
+
return func
|
|
110
|
+
|
|
111
|
+
return decorator
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class HandlerManager:
|
|
115
|
+
"""The manager for handlers."""
|
|
116
|
+
|
|
117
|
+
control_handler: Handler
|
|
118
|
+
plugin_handlers: dict[str, Handler]
|
|
119
|
+
|
|
120
|
+
def __init__(self):
|
|
121
|
+
self.langbot_handler = None
|
|
122
|
+
self.plugin_handlers = {}
|
|
123
|
+
|
|
124
|
+
def set_control_handler(self, handler: Handler) -> asyncio.Task:
|
|
125
|
+
self.control_handler = handler
|
|
126
|
+
return asyncio.create_task(handler.run())
|
|
127
|
+
|
|
128
|
+
def set_plugin_handler(self, name: str, handler: Handler) -> None:
|
|
129
|
+
self.plugin_handlers[name] = handler
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# handle connection from LangBot
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from langbot_plugin.runtime.io import handler, connection
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ControlConnectionHandler(handler.Handler):
|
|
10
|
+
"""The handler for control connection."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, connection: connection.Connection):
|
|
13
|
+
super().__init__(connection)
|
|
14
|
+
|
|
15
|
+
@self.action("ping")
|
|
16
|
+
async def ping(data: dict[str, Any]) -> handler.ActionResponse:
|
|
17
|
+
resp = await self.call_action("ping", {}, 10)
|
|
18
|
+
print("received resp: ", resp)
|
|
19
|
+
return handler.ActionResponse.success({"message": "pong"})
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# {"action": "ping", "data": {}, "seq_id": 1}
|
|
23
|
+
# {"code": 0, "message": "ok", "data": {"msg": "hello"}, "seq_id": 1}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import importlib.util
|
|
3
|
+
import os
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def import_modules_in_pkg(pkg: typing.Any) -> None:
|
|
8
|
+
"""
|
|
9
|
+
导入一个包内的所有模块
|
|
10
|
+
Args:
|
|
11
|
+
pkg: 要导入的包对象
|
|
12
|
+
"""
|
|
13
|
+
pkg_path = os.path.dirname(pkg.__file__)
|
|
14
|
+
import_dir(pkg_path)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def import_modules_in_pkgs(pkgs: typing.List) -> None:
|
|
18
|
+
for pkg in pkgs:
|
|
19
|
+
import_modules_in_pkg(pkg)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def import_dot_style_dir(dot_sep_path: str):
|
|
23
|
+
sec = dot_sep_path.split(".")
|
|
24
|
+
|
|
25
|
+
return import_dir(os.path.join(*sec))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def import_dir(path: str):
|
|
29
|
+
for file in os.listdir(path):
|
|
30
|
+
if file.endswith(".py") and file != "__init__.py":
|
|
31
|
+
full_path = os.path.join(path, file)
|
|
32
|
+
rel_path = full_path.replace(
|
|
33
|
+
os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ""
|
|
34
|
+
)
|
|
35
|
+
rel_path = rel_path[1:]
|
|
36
|
+
rel_path = rel_path.replace("/", ".")[:-3]
|
|
37
|
+
rel_path = rel_path.replace("\\", ".")
|
|
38
|
+
importlib.import_module(rel_path)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: langbot-plugin
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: This package contains the SDK, CLI for building plugins for LangBot, plus the runtime for hosting LangBot plugins
|
|
5
|
+
Author-email: Junyan Qin <rockchinq@gmail.com>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: mypy>=1.16.0
|
|
8
|
+
Requires-Dist: pydantic>=2.11.5
|
|
9
|
+
Requires-Dist: ruff>=0.11.12
|
|
10
|
+
Requires-Dist: textual>=3.2.0
|
|
11
|
+
Requires-Dist: websockets>=15.0.1
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# langbot-plugin-sdk
|
|
15
|
+
|
|
16
|
+
## Connection
|
|
17
|
+
|
|
18
|
+
### with LangBot
|
|
19
|
+
|
|
20
|
+
- Stdio
|
|
21
|
+
- Passively
|
|
22
|
+
- WebSocket
|
|
23
|
+
- Server: `:5400 /control/ws`
|
|
24
|
+
|
|
25
|
+
### with Plugins
|
|
26
|
+
|
|
27
|
+
- Stdio
|
|
28
|
+
- Actively
|
|
29
|
+
- WebSocket (Debug)
|
|
30
|
+
- Server: `:5401 /debug/ws`
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
langbot_plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
langbot_plugin/version.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
3
|
+
langbot_plugin/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
langbot_plugin/cli/__init__.py,sha256=YljXbeWxe1_FMqlfpAJKY7wIht4DNBB5APY6N24wfs8,1214
|
|
5
|
+
langbot_plugin/cli/command.py,sha256=kJhzBn7yiy0qnTWpyzAJ-IVgeyC92y1vy94Jheh5gus,132
|
|
6
|
+
langbot_plugin/cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
langbot_plugin/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
langbot_plugin/entities/io/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
langbot_plugin/entities/io/errors.py,sha256=c0SVQsA2Jbv2hNdqgt2BiqnZ4kgyAMz5gJC1iSb98zE,629
|
|
10
|
+
langbot_plugin/entities/io/req.py,sha256=bnqmdtKGh6zYFopiUt6qJljwjRWrxHNSe2XMcDFA0U8,427
|
|
11
|
+
langbot_plugin/entities/io/resp.py,sha256=ac_0F1k02pxaYH8z1k2WEGt7Hn9CXso7yaAgHNOJTNo,677
|
|
12
|
+
langbot_plugin/runtime/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
langbot_plugin/runtime/__main__.py,sha256=d1_jWWW1KdmqloXjKXT-_8nmmIY1j8q49jlzcNLMNSw,198
|
|
14
|
+
langbot_plugin/runtime/app.py,sha256=SyMzhI4uFudS4mjzcoK6Uj1-bYLjpgevT0IY6apHyG0,1691
|
|
15
|
+
langbot_plugin/runtime/controller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
langbot_plugin/runtime/controller/stdio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
langbot_plugin/runtime/controller/stdio/server.py,sha256=qhLFEvd9h9YPRa2wTtzkTuHF4o_YI3uB7uKcKiGa_L0,704
|
|
18
|
+
langbot_plugin/runtime/controller/ws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
langbot_plugin/runtime/controller/ws/server.py,sha256=f1aptaveyN99Y7xOkQld-RzDOWciq4OV29bJbEXcAQs,2610
|
|
20
|
+
langbot_plugin/runtime/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
langbot_plugin/runtime/io/connection.py,sha256=YGqDJrou55kkkjinlhZGqMi4Hg27eJxqtLU8kWFjRdw,364
|
|
22
|
+
langbot_plugin/runtime/io/handler.py,sha256=R3TKR2ZCutqv8c-LTFY4XfgmitcXjlXVFhGZympIpH4,4723
|
|
23
|
+
langbot_plugin/runtime/io/connections/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
langbot_plugin/runtime/io/connections/stdio.py,sha256=VZownx7jCAc6eVn-TY18I8yQjkxeqsdQTrQsLtJr3-E,539
|
|
25
|
+
langbot_plugin/runtime/io/connections/ws.py,sha256=3NTIrlwA8r4VXi5t7StrmKMVjUvsSx3zPJewDTTz-_A,820
|
|
26
|
+
langbot_plugin/runtime/io/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
langbot_plugin/runtime/io/handlers/control.py,sha256=sE4LNwr_sX0lsMgckbGCkWcAK-AEckF0mieloM8h7wI,737
|
|
28
|
+
langbot_plugin/runtime/service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
langbot_plugin/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
+
langbot_plugin/utils/importutil.py,sha256=1FLb67yL12UoOhVOo7rwrNroIkhFkPTqJG4JxuBuWm8,1018
|
|
31
|
+
langbot_plugin-0.1.0.dist-info/METADATA,sha256=BKL2QcTG3PZ0U4klwigV4Ux39BI1mmPEEUN87FUmUBY,664
|
|
32
|
+
langbot_plugin-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
33
|
+
langbot_plugin-0.1.0.dist-info/entry_points.txt,sha256=EfG3gGtCzia5YAO0Llvi4YqfaObIw-Kp-CkVqTEYcbE,48
|
|
34
|
+
langbot_plugin-0.1.0.dist-info/RECORD,,
|