steerable-plugin-sdk 0.6.29__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.
- steerable_plugin_sdk/__init__.py +27 -0
- steerable_plugin_sdk/rpc.py +124 -0
- steerable_plugin_sdk/schema.py +86 -0
- steerable_plugin_sdk/tools.py +218 -0
- steerable_plugin_sdk-0.6.29.dist-info/METADATA +28 -0
- steerable_plugin_sdk-0.6.29.dist-info/RECORD +8 -0
- steerable_plugin_sdk-0.6.29.dist-info/WHEEL +5 -0
- steerable_plugin_sdk-0.6.29.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Pure-Python Steerable plugin authoring and plugin-host RPC SDK."""
|
|
2
|
+
|
|
3
|
+
from .rpc import PLUGIN_HOST_PROTOCOL_VERSION, PluginHostRpcClient, RpcRouterProxy
|
|
4
|
+
from .schema import derive_schema
|
|
5
|
+
from .tools import (
|
|
6
|
+
STEERABLE_TOOLS_ENTRY_POINT_GROUP,
|
|
7
|
+
LocalPluginRouter,
|
|
8
|
+
RegisteredTool,
|
|
9
|
+
ToolDescriptor,
|
|
10
|
+
ToolExposure,
|
|
11
|
+
ToolRegistrationError,
|
|
12
|
+
tool,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"PLUGIN_HOST_PROTOCOL_VERSION",
|
|
17
|
+
"STEERABLE_TOOLS_ENTRY_POINT_GROUP",
|
|
18
|
+
"LocalPluginRouter",
|
|
19
|
+
"PluginHostRpcClient",
|
|
20
|
+
"RegisteredTool",
|
|
21
|
+
"RpcRouterProxy",
|
|
22
|
+
"ToolDescriptor",
|
|
23
|
+
"ToolExposure",
|
|
24
|
+
"ToolRegistrationError",
|
|
25
|
+
"derive_schema",
|
|
26
|
+
"tool",
|
|
27
|
+
]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Plugin-host JSON-RPC client and sidecar router projection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
6
|
+
from typing import Any, Protocol
|
|
7
|
+
|
|
8
|
+
from steerable_agent_protocol.generated import ToolResult
|
|
9
|
+
|
|
10
|
+
from .tools import ToolDescriptor
|
|
11
|
+
|
|
12
|
+
PLUGIN_HOST_PROTOCOL_VERSION = "0.1.0"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RpcTransport(Protocol):
|
|
16
|
+
"""Request transport supplied by the plugin-host process owner."""
|
|
17
|
+
|
|
18
|
+
async def request(self, method: str, params: dict[str, Any]) -> Any:
|
|
19
|
+
"""Issue one JSON-RPC request and return its result."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RemoteRegistrationRouter(Protocol):
|
|
23
|
+
"""Runtime router surface needed to project remote plugin tools."""
|
|
24
|
+
|
|
25
|
+
def register_remote(
|
|
26
|
+
self,
|
|
27
|
+
name: str,
|
|
28
|
+
invoker: Callable[[str, dict[str, Any]], Awaitable[Any]],
|
|
29
|
+
**kwargs: Any,
|
|
30
|
+
) -> Any:
|
|
31
|
+
"""Register one remotely invoked tool."""
|
|
32
|
+
|
|
33
|
+
def unregister(self, name: str) -> None:
|
|
34
|
+
"""Remove one projected tool."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class PluginHostRpcClient:
|
|
38
|
+
"""Typed client for the frozen plugin-host RPC methods."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, transport: RpcTransport) -> None:
|
|
41
|
+
self._transport = transport
|
|
42
|
+
|
|
43
|
+
async def ping(self) -> dict[str, Any]:
|
|
44
|
+
"""Negotiate the plugin-host protocol version."""
|
|
45
|
+
return await self._transport.request(
|
|
46
|
+
"plugin.host.ping",
|
|
47
|
+
{"protocolVersion": PLUGIN_HOST_PROTOCOL_VERSION},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
async def describe_tools(self) -> list[ToolDescriptor]:
|
|
51
|
+
"""Read every tool currently enabled in the plugin host."""
|
|
52
|
+
result = await self._transport.request("plugin.tools.describe", {})
|
|
53
|
+
return [
|
|
54
|
+
ToolDescriptor.from_wire(value)
|
|
55
|
+
for value in result.get("tools", [])
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
async def invoke(
|
|
59
|
+
self,
|
|
60
|
+
name: str,
|
|
61
|
+
arguments: dict[str, Any],
|
|
62
|
+
context: dict[str, Any] | None = None,
|
|
63
|
+
) -> ToolResult:
|
|
64
|
+
"""Invoke one plugin tool."""
|
|
65
|
+
result = await self._transport.request(
|
|
66
|
+
"plugin.tool.invoke",
|
|
67
|
+
{"name": name, "arguments": arguments, "context": context or {}},
|
|
68
|
+
)
|
|
69
|
+
return ToolResult(**result)
|
|
70
|
+
|
|
71
|
+
async def list_plugins(self) -> dict[str, Any]:
|
|
72
|
+
"""Return plugin lifecycle records."""
|
|
73
|
+
return await self._transport.request("plugin.list", {})
|
|
74
|
+
|
|
75
|
+
async def enable(self, name: str) -> dict[str, Any]:
|
|
76
|
+
"""Enable one plugin."""
|
|
77
|
+
return await self._lifecycle("plugin.enable", name)
|
|
78
|
+
|
|
79
|
+
async def disable(self, name: str) -> dict[str, Any]:
|
|
80
|
+
"""Disable one plugin."""
|
|
81
|
+
return await self._lifecycle("plugin.disable", name)
|
|
82
|
+
|
|
83
|
+
async def reload(self, name: str) -> dict[str, Any]:
|
|
84
|
+
"""Reload one plugin."""
|
|
85
|
+
return await self._lifecycle("plugin.reload", name)
|
|
86
|
+
|
|
87
|
+
async def _lifecycle(self, method: str, name: str) -> dict[str, Any]:
|
|
88
|
+
return await self._transport.request(method, {"name": name})
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class RpcRouterProxy:
|
|
92
|
+
"""Project plugin-host descriptors onto a runtime remote-tool router."""
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
client: PluginHostRpcClient,
|
|
97
|
+
router: RemoteRegistrationRouter,
|
|
98
|
+
) -> None:
|
|
99
|
+
self._client = client
|
|
100
|
+
self._router = router
|
|
101
|
+
self._names: list[str] = []
|
|
102
|
+
|
|
103
|
+
async def sync_tools(self) -> list[str]:
|
|
104
|
+
"""Replace projected registrations with the host's current catalog."""
|
|
105
|
+
for name in self._names:
|
|
106
|
+
self._router.unregister(name)
|
|
107
|
+
self._names = []
|
|
108
|
+
for descriptor in await self._client.describe_tools():
|
|
109
|
+
self._router.register_remote(
|
|
110
|
+
descriptor.name,
|
|
111
|
+
self._client.invoke,
|
|
112
|
+
mode=descriptor.mode,
|
|
113
|
+
description=descriptor.description,
|
|
114
|
+
schema=descriptor.schema,
|
|
115
|
+
require_consent=descriptor.require_consent,
|
|
116
|
+
concurrency_safe=descriptor.concurrency_safe,
|
|
117
|
+
exposure=descriptor.exposure,
|
|
118
|
+
metadata={
|
|
119
|
+
"plugin": descriptor.plugin,
|
|
120
|
+
"pluginHost": True,
|
|
121
|
+
},
|
|
122
|
+
)
|
|
123
|
+
self._names.append(descriptor.name)
|
|
124
|
+
return list(self._names)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Conservative JSON Schema derivation for plugin tool handlers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import types
|
|
7
|
+
import typing
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from typing import Any, get_args, get_origin
|
|
10
|
+
|
|
11
|
+
_SCALAR_TYPES: dict[type, str] = {
|
|
12
|
+
str: "string",
|
|
13
|
+
int: "integer",
|
|
14
|
+
float: "number",
|
|
15
|
+
bool: "boolean",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _annotation_to_schema(annotation: Any) -> dict[str, Any]:
|
|
20
|
+
if annotation is inspect.Parameter.empty or annotation is Any:
|
|
21
|
+
return {}
|
|
22
|
+
origin = get_origin(annotation)
|
|
23
|
+
if origin is typing.Union or origin is types.UnionType:
|
|
24
|
+
members = list(get_args(annotation))
|
|
25
|
+
nullable = type(None) in members
|
|
26
|
+
non_null = [member for member in members if member is not type(None)]
|
|
27
|
+
if len(non_null) == 1:
|
|
28
|
+
inner = _annotation_to_schema(non_null[0])
|
|
29
|
+
inner_type = inner.get("type")
|
|
30
|
+
if nullable and isinstance(inner_type, str):
|
|
31
|
+
return {**inner, "type": [inner_type, "null"]}
|
|
32
|
+
return inner if inner else ({"type": "null"} if nullable else {})
|
|
33
|
+
types_seen = [
|
|
34
|
+
_SCALAR_TYPES[member]
|
|
35
|
+
for member in non_null
|
|
36
|
+
if member in _SCALAR_TYPES
|
|
37
|
+
]
|
|
38
|
+
if nullable:
|
|
39
|
+
types_seen.append("null")
|
|
40
|
+
return {"type": types_seen} if types_seen else {}
|
|
41
|
+
if origin is list:
|
|
42
|
+
args = get_args(annotation)
|
|
43
|
+
items = _annotation_to_schema(args[0]) if args else {}
|
|
44
|
+
return {"type": "array", **({"items": items} if items else {})}
|
|
45
|
+
if origin is dict:
|
|
46
|
+
return {"type": "object"}
|
|
47
|
+
if origin is typing.Literal:
|
|
48
|
+
values = list(get_args(annotation))
|
|
49
|
+
schema: dict[str, Any] = {"enum": values}
|
|
50
|
+
if values and all(isinstance(value, type(values[0])) for value in values):
|
|
51
|
+
literal_type = _SCALAR_TYPES.get(type(values[0]))
|
|
52
|
+
if literal_type:
|
|
53
|
+
schema["type"] = literal_type
|
|
54
|
+
return schema
|
|
55
|
+
if annotation in _SCALAR_TYPES:
|
|
56
|
+
return {"type": _SCALAR_TYPES[annotation]}
|
|
57
|
+
if annotation is list:
|
|
58
|
+
return {"type": "array"}
|
|
59
|
+
if annotation is dict:
|
|
60
|
+
return {"type": "object"}
|
|
61
|
+
return {}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def derive_schema(handler: Callable[..., Any]) -> dict[str, Any]:
|
|
65
|
+
"""Build an object schema from the model-supplied handler parameters."""
|
|
66
|
+
try:
|
|
67
|
+
hints = typing.get_type_hints(handler)
|
|
68
|
+
except (NameError, TypeError, AttributeError, SyntaxError, ValueError):
|
|
69
|
+
hints = {}
|
|
70
|
+
properties: dict[str, Any] = {}
|
|
71
|
+
required: list[str] = []
|
|
72
|
+
for parameter in inspect.signature(handler).parameters.values():
|
|
73
|
+
if parameter.kind in (
|
|
74
|
+
inspect.Parameter.VAR_POSITIONAL,
|
|
75
|
+
inspect.Parameter.VAR_KEYWORD,
|
|
76
|
+
) or parameter.name == "context":
|
|
77
|
+
continue
|
|
78
|
+
properties[parameter.name] = _annotation_to_schema(
|
|
79
|
+
hints.get(parameter.name, parameter.annotation)
|
|
80
|
+
)
|
|
81
|
+
if parameter.default is inspect.Parameter.empty:
|
|
82
|
+
required.append(parameter.name)
|
|
83
|
+
schema: dict[str, Any] = {"type": "object", "properties": properties}
|
|
84
|
+
if required:
|
|
85
|
+
schema["required"] = required
|
|
86
|
+
return schema
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Plugin tool declaration and local plugin-host dispatch."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from collections.abc import Awaitable, Callable
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Any, Literal, Protocol
|
|
9
|
+
|
|
10
|
+
from steerable_agent_harness.policy import ToolMode, decide_tool_mode
|
|
11
|
+
from steerable_agent_protocol.generated import ToolResult
|
|
12
|
+
|
|
13
|
+
from .schema import derive_schema
|
|
14
|
+
|
|
15
|
+
ToolHandler = Callable[..., Any] | Callable[..., Awaitable[Any]]
|
|
16
|
+
ToolExposure = Literal["direct", "deferred", "hidden"]
|
|
17
|
+
STEERABLE_TOOLS_ENTRY_POINT_GROUP = "steerable.tools"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ToolRegistrationError(RuntimeError):
|
|
21
|
+
"""A plugin attempted an invalid or duplicate tool registration."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RegistrationRouter(Protocol):
|
|
25
|
+
"""Router methods consumed by the SDK's decorator."""
|
|
26
|
+
|
|
27
|
+
def register(self, handler: ToolHandler, **kwargs: Any) -> Any:
|
|
28
|
+
"""Register one handler and return the host's registration record."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(slots=True)
|
|
32
|
+
class ToolDescriptor:
|
|
33
|
+
"""Wire-safe metadata advertised by a plugin host."""
|
|
34
|
+
|
|
35
|
+
name: str
|
|
36
|
+
description: str
|
|
37
|
+
schema: dict[str, Any]
|
|
38
|
+
mode: ToolMode
|
|
39
|
+
exposure: ToolExposure
|
|
40
|
+
require_consent: bool
|
|
41
|
+
concurrency_safe: bool
|
|
42
|
+
plugin: str | None = None
|
|
43
|
+
|
|
44
|
+
def to_wire(self) -> dict[str, Any]:
|
|
45
|
+
"""Return the camelCase plugin-host representation."""
|
|
46
|
+
return {
|
|
47
|
+
"name": self.name,
|
|
48
|
+
"description": self.description,
|
|
49
|
+
"schema": self.schema,
|
|
50
|
+
"mode": self.mode,
|
|
51
|
+
"exposure": self.exposure,
|
|
52
|
+
"requireConsent": self.require_consent,
|
|
53
|
+
"concurrencySafe": self.concurrency_safe,
|
|
54
|
+
"plugin": self.plugin,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def from_wire(cls, value: dict[str, Any]) -> ToolDescriptor:
|
|
59
|
+
"""Decode one plugin-host descriptor."""
|
|
60
|
+
return cls(
|
|
61
|
+
name=str(value["name"]),
|
|
62
|
+
description=str(value.get("description") or ""),
|
|
63
|
+
schema=dict(value.get("schema") or {}),
|
|
64
|
+
mode=value.get("mode") or decide_tool_mode(str(value["name"])),
|
|
65
|
+
exposure=value.get("exposure") or "direct",
|
|
66
|
+
require_consent=bool(value.get("requireConsent", False)),
|
|
67
|
+
concurrency_safe=bool(value.get("concurrencySafe", False)),
|
|
68
|
+
plugin=str(value["plugin"]) if value.get("plugin") else None,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(slots=True)
|
|
73
|
+
class RegisteredTool:
|
|
74
|
+
"""One plugin-host registration and its local handler."""
|
|
75
|
+
|
|
76
|
+
descriptor: ToolDescriptor
|
|
77
|
+
handler: ToolHandler = field(repr=False)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class LocalPluginRouter:
|
|
81
|
+
"""Pure-Python registry used inside the future plugin-host process."""
|
|
82
|
+
|
|
83
|
+
def __init__(self) -> None:
|
|
84
|
+
self._tools: dict[str, RegisteredTool] = {}
|
|
85
|
+
|
|
86
|
+
def register(
|
|
87
|
+
self,
|
|
88
|
+
handler: ToolHandler,
|
|
89
|
+
*,
|
|
90
|
+
name: str | None = None,
|
|
91
|
+
mode: ToolMode | None = None,
|
|
92
|
+
description: str | None = None,
|
|
93
|
+
schema: dict[str, Any] | None = None,
|
|
94
|
+
require_consent: bool | None = None,
|
|
95
|
+
concurrency_safe: bool | None = None,
|
|
96
|
+
exposure: ToolExposure = "direct",
|
|
97
|
+
plugin: str | None = None,
|
|
98
|
+
**_ignored: Any,
|
|
99
|
+
) -> RegisteredTool:
|
|
100
|
+
"""Register one plugin handler."""
|
|
101
|
+
resolved_name = name or getattr(handler, "__name__", None)
|
|
102
|
+
if not resolved_name:
|
|
103
|
+
raise ToolRegistrationError("tool handler must have a name")
|
|
104
|
+
if resolved_name in self._tools:
|
|
105
|
+
raise ToolRegistrationError(f"tool already registered: {resolved_name}")
|
|
106
|
+
resolved_mode = mode or decide_tool_mode(resolved_name)
|
|
107
|
+
descriptor = ToolDescriptor(
|
|
108
|
+
name=resolved_name,
|
|
109
|
+
description=description or (inspect.getdoc(handler) or "").strip(),
|
|
110
|
+
schema=schema if schema is not None else derive_schema(handler),
|
|
111
|
+
mode=resolved_mode,
|
|
112
|
+
exposure=exposure,
|
|
113
|
+
require_consent=(
|
|
114
|
+
require_consent
|
|
115
|
+
if require_consent is not None
|
|
116
|
+
else resolved_mode == "destructive"
|
|
117
|
+
),
|
|
118
|
+
concurrency_safe=(
|
|
119
|
+
concurrency_safe
|
|
120
|
+
if concurrency_safe is not None
|
|
121
|
+
else resolved_mode == "read"
|
|
122
|
+
),
|
|
123
|
+
plugin=plugin,
|
|
124
|
+
)
|
|
125
|
+
registered = RegisteredTool(descriptor=descriptor, handler=handler)
|
|
126
|
+
self._tools[resolved_name] = registered
|
|
127
|
+
return registered
|
|
128
|
+
|
|
129
|
+
def unregister(self, name: str) -> None:
|
|
130
|
+
"""Remove a handler if present."""
|
|
131
|
+
self._tools.pop(name, None)
|
|
132
|
+
|
|
133
|
+
def descriptors(self) -> list[ToolDescriptor]:
|
|
134
|
+
"""Return registrations in insertion order."""
|
|
135
|
+
return [registered.descriptor for registered in self._tools.values()]
|
|
136
|
+
|
|
137
|
+
async def dispatch(
|
|
138
|
+
self,
|
|
139
|
+
name: str,
|
|
140
|
+
arguments: dict[str, Any],
|
|
141
|
+
context: dict[str, Any] | None = None,
|
|
142
|
+
) -> ToolResult:
|
|
143
|
+
"""Invoke one local plugin handler and normalize its result."""
|
|
144
|
+
registered = self._tools.get(name)
|
|
145
|
+
if registered is None:
|
|
146
|
+
return ToolResult(
|
|
147
|
+
success=False,
|
|
148
|
+
error=f"unknown plugin tool: {name}",
|
|
149
|
+
terminal=False,
|
|
150
|
+
needsFollowup=True,
|
|
151
|
+
)
|
|
152
|
+
handler = registered.handler
|
|
153
|
+
signature = inspect.signature(handler)
|
|
154
|
+
accepts_kwargs = any(
|
|
155
|
+
parameter.kind is inspect.Parameter.VAR_KEYWORD
|
|
156
|
+
for parameter in signature.parameters.values()
|
|
157
|
+
)
|
|
158
|
+
kwargs = {
|
|
159
|
+
parameter.name: (
|
|
160
|
+
(context or {})
|
|
161
|
+
if parameter.name == "context"
|
|
162
|
+
else arguments[parameter.name]
|
|
163
|
+
)
|
|
164
|
+
for parameter in signature.parameters.values()
|
|
165
|
+
if parameter.kind
|
|
166
|
+
not in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
|
|
167
|
+
and (parameter.name == "context" or parameter.name in arguments)
|
|
168
|
+
}
|
|
169
|
+
if accepts_kwargs:
|
|
170
|
+
for key, value in arguments.items():
|
|
171
|
+
kwargs.setdefault(key, value)
|
|
172
|
+
try:
|
|
173
|
+
result = handler(**kwargs)
|
|
174
|
+
if inspect.isawaitable(result):
|
|
175
|
+
result = await result
|
|
176
|
+
except Exception as exc:
|
|
177
|
+
return ToolResult(
|
|
178
|
+
success=False,
|
|
179
|
+
error=str(exc),
|
|
180
|
+
terminal=False,
|
|
181
|
+
needsFollowup=True,
|
|
182
|
+
)
|
|
183
|
+
if isinstance(result, ToolResult):
|
|
184
|
+
return result
|
|
185
|
+
if isinstance(result, dict) and "success" in result:
|
|
186
|
+
return ToolResult(**result)
|
|
187
|
+
return ToolResult(success=True, data={"value": result})
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def tool(
|
|
191
|
+
*,
|
|
192
|
+
name: str | None = None,
|
|
193
|
+
mode: ToolMode | None = None,
|
|
194
|
+
description: str | None = None,
|
|
195
|
+
schema: dict[str, Any] | None = None,
|
|
196
|
+
require_consent: bool | None = None,
|
|
197
|
+
concurrency_safe: bool | None = None,
|
|
198
|
+
exposure: ToolExposure = "direct",
|
|
199
|
+
router: RegistrationRouter | None = None,
|
|
200
|
+
) -> Callable[[ToolHandler], ToolHandler]:
|
|
201
|
+
"""Decorate and optionally register a plugin handler."""
|
|
202
|
+
|
|
203
|
+
def decorate(handler: ToolHandler) -> ToolHandler:
|
|
204
|
+
metadata = {
|
|
205
|
+
"name": name,
|
|
206
|
+
"mode": mode,
|
|
207
|
+
"description": description,
|
|
208
|
+
"schema": schema,
|
|
209
|
+
"require_consent": require_consent,
|
|
210
|
+
"concurrency_safe": concurrency_safe,
|
|
211
|
+
"exposure": exposure,
|
|
212
|
+
}
|
|
213
|
+
handler.__steerable_tool_meta__ = metadata
|
|
214
|
+
if router is not None:
|
|
215
|
+
router.register(handler, **metadata)
|
|
216
|
+
return handler
|
|
217
|
+
|
|
218
|
+
return decorate
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: steerable-plugin-sdk
|
|
3
|
+
Version: 0.6.29
|
|
4
|
+
Summary: Pure-Python authoring and RPC SDK for Steerable tool plugins
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: steerable-agent-harness<1.0.0,>=0.1.0
|
|
8
|
+
Requires-Dist: steerable-agent-protocol<1.0.0,>=0.1.0
|
|
9
|
+
|
|
10
|
+
# Steerable Plugin SDK
|
|
11
|
+
|
|
12
|
+
Pure-Python plugin authoring surface for Steerable tools. This package does
|
|
13
|
+
not depend on `steerable-agent-runtime` or its native CoreLoop wheel.
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from steerable_plugin_sdk import tool
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def register(router):
|
|
20
|
+
@tool(router=router, description="Greet by name")
|
|
21
|
+
async def greet(name: str) -> str:
|
|
22
|
+
return f"hello {name}"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Packaged plugins publish `register` through the `steerable.tools` entry-point
|
|
26
|
+
group. `LocalPluginRouter` is the plugin-host registry; `PluginHostRpcClient`
|
|
27
|
+
and `RpcRouterProxy` bridge its descriptors and invocations to a sidecar
|
|
28
|
+
router.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
steerable_plugin_sdk/__init__.py,sha256=lZiY0ffvcAaWXtiFx7yMsXfzuvNaqHr4OwYNcVs4CO0,660
|
|
2
|
+
steerable_plugin_sdk/rpc.py,sha256=-sRMoC6THWezROfa-hFucxxzK7ZzX5XziqKNFfrySdI,4159
|
|
3
|
+
steerable_plugin_sdk/schema.py,sha256=Kki1-M6Lt0z6iQ5iRG7eyBHSDoL505zsOyAhRXjbqYo,3175
|
|
4
|
+
steerable_plugin_sdk/tools.py,sha256=6qkzBvqY_oNax0ACV10eqk7bRwoFYjy6tw6FRICGY3s,7560
|
|
5
|
+
steerable_plugin_sdk-0.6.29.dist-info/METADATA,sha256=zTdxjFsCfJ-Y76jzi0apkhEwnwGhln94NoIEz1_sGUo,916
|
|
6
|
+
steerable_plugin_sdk-0.6.29.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
7
|
+
steerable_plugin_sdk-0.6.29.dist-info/top_level.txt,sha256=MrP64sC9BWffVptG1NjXalwH_NHOEflXHGB7mjKedyw,21
|
|
8
|
+
steerable_plugin_sdk-0.6.29.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
steerable_plugin_sdk
|