bma-executor-sdk 0.4.0__tar.gz → 0.4.2__tar.gz
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.
- bma_executor_sdk-0.4.2/PKG-INFO +136 -0
- bma_executor_sdk-0.4.2/README.md +125 -0
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/pyproject.toml +1 -1
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/src/bma_executor_sdk/client.py +12 -1
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/tests/test_executor_runtime.py +17 -0
- bma_executor_sdk-0.4.0/PKG-INFO +0 -28
- bma_executor_sdk-0.4.0/README.md +0 -17
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/.gitignore +0 -0
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/examples/agent_minimal.py +0 -0
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/examples/executor_minimal.py +0 -0
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/examples/harness_loopback.py +0 -0
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/src/bma_executor_sdk/__init__.py +0 -0
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/src/bma_executor_sdk/_rpc.py +0 -0
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/src/bma_executor_sdk/agent.py +0 -0
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/src/bma_executor_sdk/generated.py +0 -0
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/src/bma_executor_sdk/harness.py +0 -0
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/src/bma_executor_sdk/transport.py +0 -0
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/tests/executor_helpers.py +0 -0
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/tests/test_executor_conformance.py +0 -0
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/tests/test_executor_contract.py +0 -0
- {bma_executor_sdk-0.4.0 → bma_executor_sdk-0.4.2}/tests/test_executor_interop.py +0 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: bma-executor-sdk
|
|
3
|
+
Version: 0.4.2
|
|
4
|
+
Summary: BMA executor protocol (bma.executor.v1.1): executor, harness and agent ends on asyncio with WebSocket, Unix socket and in-process transports.
|
|
5
|
+
Project-URL: Repository, https://github.com/beeos-ai/bma-sdks
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: httpx<1,>=0.27
|
|
9
|
+
Requires-Dist: websockets<17,>=14
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# bma-executor-sdk (Python)
|
|
13
|
+
|
|
14
|
+
[](https://pypi.org/project/bma-executor-sdk/)
|
|
15
|
+
|
|
16
|
+
asyncio executor (`ExecutorClient`), harness (`Harness`) and agent ends of the BMA Executor Protocol `bma.executor.v1.1`, the counterpart of the [Go `executor` module](../README.md). Protocol types are TypedDicts generated into `bma_executor_sdk/generated.py` from the contract in [`contracts/bma/executor`](../../contracts/bma/executor), so messages are plain JSON dicts.
|
|
17
|
+
|
|
18
|
+
| | |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| Version | 0.4.2 (tag `executor-python/v0.4.2`), see [CHANGELOG](../../CHANGELOG.md) |
|
|
21
|
+
| Protocol | `bma.executor.v1.1` (contract 1.1.0, [`lock.json`](../../contracts/bma/executor/lock.json)) |
|
|
22
|
+
| Python | 3.10 or newer; depends on `httpx` and `websockets` |
|
|
23
|
+
| Integration guide | [docs/executor](../../docs/executor/README.md) ([中文](../../docs/executor/README.zh-CN.md)) |
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
pip install bma-executor-sdk==0.4.2
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The REST / façade client is the separate package [`bma-sdk`](../../python).
|
|
30
|
+
|
|
31
|
+
## Executor quick start
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import asyncio, os
|
|
35
|
+
from bma_executor_sdk import ExecutorClient
|
|
36
|
+
|
|
37
|
+
async def main() -> None:
|
|
38
|
+
client = ExecutorClient(
|
|
39
|
+
environment_key=os.environ["BMA_ENVIRONMENT_KEY"],
|
|
40
|
+
environment_id=os.environ["BMA_ENVIRONMENT_ID"],
|
|
41
|
+
executor={"name": "my-executor", "os": "linux", "arch": "arm64", "version": "0.1.0"},
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
async def on_tool_call(call):
|
|
45
|
+
return {"session_id": call["session_id"], "tool_call_id": call["tool_call_id"],
|
|
46
|
+
"outcome": "succeeded", "is_error": False, "content": [{"type": "text", "text": "ok"}]}
|
|
47
|
+
|
|
48
|
+
client.on_tool_call(on_tool_call)
|
|
49
|
+
await client.start()
|
|
50
|
+
await asyncio.Event().wait()
|
|
51
|
+
|
|
52
|
+
asyncio.run(main())
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
A complete version that runs bash and stops the process on cancellation is [`examples/executor_minimal.py`](examples/executor_minimal.py); set `BMA_HARNESS_URL` (`ws://…` or `unix:///path.sock`) to connect it to a local harness instead of BMA.
|
|
56
|
+
|
|
57
|
+
Behavior matches the Go SDK:
|
|
58
|
+
|
|
59
|
+
- `tool.cancel` cancels the handler's task and reports `outcome: "canceled"`; duplicate `tool.call` ids run once.
|
|
60
|
+
- `tool.result` retries `-32009` / `-32004` / `retryable` errors and hands a lost result to `on_tool_result_lost`.
|
|
61
|
+
- Reconnect resumes with `resume.executor_id` / `active_sessions`; close codes 1008, 4002, 4003 and 4004 stop the client (4001 and 4005 reconnect).
|
|
62
|
+
- Call `session_ready` after setup and `session_ended` after cleanup. `on_request(method, handler)` serves `process.*`, `terminal.*` or `skill.install` yourself; there is no execution `Core` in Python.
|
|
63
|
+
|
|
64
|
+
## Harness quick start
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from bma_executor_sdk import Harness
|
|
68
|
+
|
|
69
|
+
harness = Harness(authenticate=authenticate) # authenticate(ConnectRequest) -> Principal; omit only in-process or on a private socket
|
|
70
|
+
server = await harness.serve("127.0.0.1", 0) # or await harness.serve_unix(path)
|
|
71
|
+
|
|
72
|
+
executor = await harness.wait_executor()
|
|
73
|
+
session = await executor.assign_session({"session_id": "ses_1"})
|
|
74
|
+
await session.ready()
|
|
75
|
+
result = await session.call_tool({"tool": "bash", "input": {"command": "ls"}})
|
|
76
|
+
mcp = await session.mcp("echo") # raw MCP JSON-RPC
|
|
77
|
+
await session.stop("done")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Semantics match the Go `Harness`: `call_tool` is idempotent by `tool_call_id`; sessions and pending calls survive a reconnect within the lease; lease expiry fails pending calls with `ToolOutcomeUnknownError`.
|
|
81
|
+
|
|
82
|
+
## Transports
|
|
83
|
+
|
|
84
|
+
Chosen by the channel URL of the token source:
|
|
85
|
+
|
|
86
|
+
| Transport | Executor | Harness |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| WebSocket (`ws://`, `wss://`) | default | `await harness.serve(host, port)` |
|
|
89
|
+
| WebSocket over a Unix domain socket (`unix:///path.sock`) | `token_source=static_token("unix:///path.sock", token)` | `await harness.serve_unix(path)` |
|
|
90
|
+
| In process | `dialer=harness.inprocess_dialer()` | — |
|
|
91
|
+
|
|
92
|
+
The executor-listens local socket and Windows named pipes are not implemented in Python (use the Go or TypeScript SDK). See [local connection](../../docs/executor/local-connection.md).
|
|
93
|
+
|
|
94
|
+
## Agent executors
|
|
95
|
+
|
|
96
|
+
`client.serve_agent(handler, capability)` serves the `agent` capability; `handler` implements `open_turn`, `run_turn` and `close_session` (async; cancellation is task cancellation), and `run_turn` uses `turn.emit`, `await turn.delta(...)`, `turn.inputs` (an `asyncio.Queue`) and `turn.request_interaction`. The harness side is `Harness(agent=platform)` with an `AgentPlatform`. See [agent capability](../../docs/executor/agent-capability.md).
|
|
97
|
+
|
|
98
|
+
## Go ↔ Python
|
|
99
|
+
|
|
100
|
+
| Go (`bmaexecutor`) | Python (`bma_executor_sdk`) |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| `NewClient(Options{...})` | `ExecutorClient(environment_id=..., executor=..., ...)` |
|
|
103
|
+
| `Options.TokenSource`, `StaticToken(url, token)` | `token_source=`, `static_token(url, token)` |
|
|
104
|
+
| `Client.Start` / `Stop` / `State` / `ExecutorID` / `ConnectionID` / `ActiveSessions` | `start()` / `stop(reason)` / `state` / `executor_id` / `connection_id` / `active_sessions` |
|
|
105
|
+
| `OnToolCall` / `OnSessionAssign` / `OnSessionStop` / `OnDrain` / `OnRequest` / `OnNotification` | `on_tool_call` / `on_session_assign` / `on_session_stop` / `on_drain` / `on_request` / `on_notification` |
|
|
106
|
+
| `Call` / `Notify` / `ReportToolResult` / `SessionReady` / `SessionEnded` / `OpenMCPChannel` | `call` / `notify` / `report_tool_result` / `session_ready` / `session_ended` / `open_mcp_channel` |
|
|
107
|
+
| `ServeAgent(handler, AgentCapability)` | `serve_agent(handler, capability)` |
|
|
108
|
+
| `AgentHandler.OpenTurn` / `RunTurn` / `CloseSession` | `open_turn` / `run_turn` / `close_session` |
|
|
109
|
+
| `AgentTurn.Emit` / `EmitWithID` / `NewEventID` / `Delta` / `Flush` / `Inputs()` / `RequestInteraction` / `CancelInteraction` | `emit` / `emit_with_id` / `new_event_id` / `await delta` / `await flush` / `inputs` / `request_interaction` / `cancel_interaction` |
|
|
110
|
+
| `NewHarness(HarnessOptions{...})`, `http.Handler` | `Harness(...)`, `await serve(host, port)` / `serve_unix(path)` / `inprocess_dialer()` |
|
|
111
|
+
| `HarnessOptions.Authenticate(*http.Request) (Principal, error)` | `authenticate(ConnectRequest) -> Principal` (raise to refuse) |
|
|
112
|
+
| `Harness.Executors` / `WaitExecutor` | `executors()` / `wait_executor()` |
|
|
113
|
+
| `Executor.AssignSession` / `Drain` / `Capabilities` / `Protocol` | `assign_session` / `drain` / `capabilities` / `protocol` |
|
|
114
|
+
| `Executor.StartTurn` / `SendTurnInput` / `CancelTurn` / `CloseAgentSession` / `RespondInteraction` / `AgentCapability` | `start_turn` / `send_turn_input` / `cancel_turn` / `close_agent_session` / `respond_interaction` / `agent_capability()` |
|
|
115
|
+
| `Session.Ready` / `CallTool` / `CancelTool` / `Stop` / `MCP` / `Done` / `EndReason` | `ready()` / `call_tool` / `cancel_tool` / `stop` / `mcp` / `wait_ended()`, `ended` / `end_reason` |
|
|
116
|
+
| `AgentPlatform` | `AgentPlatform` (Protocol: `acked_sequence`, `agent_event`, `agent_delta`, `interaction_requested`, `interaction_canceled`) |
|
|
117
|
+
| `ErrNotConnected` / `ErrClientStopped` / `ErrSessionEnded` / `ErrToolOutcomeUnknown` / `ErrAssignRejected` / `*RPCError` | `NotConnectedError` / `ClientStoppedError` / `SessionEndedError` / `ToolOutcomeUnknownError` / `AssignRejectedError` / `RPCError` |
|
|
118
|
+
| `Core`, `NewProcessCapability`, `ListenUnixSocket`, `Harness.DialExecutor` | not provided |
|
|
119
|
+
|
|
120
|
+
## Examples
|
|
121
|
+
|
|
122
|
+
Run from [`examples`](examples) after `uv sync` (or `pip install bma-executor-sdk`):
|
|
123
|
+
|
|
124
|
+
| Example | What it shows |
|
|
125
|
+
| --- | --- |
|
|
126
|
+
| [`executor_minimal.py`](examples/executor_minimal.py) | executor answering bash calls, against BMA or a local harness |
|
|
127
|
+
| [`harness_loopback.py`](examples/harness_loopback.py) | brain and hand in one process, in-process or `--unix PATH` |
|
|
128
|
+
| [`agent_minimal.py`](examples/agent_minimal.py) | agent capability with an in-memory platform |
|
|
129
|
+
|
|
130
|
+
## Tests
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
uv sync && uv run pytest -q
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
[`tests/test_executor_interop.py`](tests/test_executor_interop.py) runs the Python executor against the Go harness (WebSocket and Unix socket) and the Python harness against the Go executor; it needs Go.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# bma-executor-sdk (Python)
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/bma-executor-sdk/)
|
|
4
|
+
|
|
5
|
+
asyncio executor (`ExecutorClient`), harness (`Harness`) and agent ends of the BMA Executor Protocol `bma.executor.v1.1`, the counterpart of the [Go `executor` module](../README.md). Protocol types are TypedDicts generated into `bma_executor_sdk/generated.py` from the contract in [`contracts/bma/executor`](../../contracts/bma/executor), so messages are plain JSON dicts.
|
|
6
|
+
|
|
7
|
+
| | |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| Version | 0.4.2 (tag `executor-python/v0.4.2`), see [CHANGELOG](../../CHANGELOG.md) |
|
|
10
|
+
| Protocol | `bma.executor.v1.1` (contract 1.1.0, [`lock.json`](../../contracts/bma/executor/lock.json)) |
|
|
11
|
+
| Python | 3.10 or newer; depends on `httpx` and `websockets` |
|
|
12
|
+
| Integration guide | [docs/executor](../../docs/executor/README.md) ([中文](../../docs/executor/README.zh-CN.md)) |
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
pip install bma-executor-sdk==0.4.2
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The REST / façade client is the separate package [`bma-sdk`](../../python).
|
|
19
|
+
|
|
20
|
+
## Executor quick start
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import asyncio, os
|
|
24
|
+
from bma_executor_sdk import ExecutorClient
|
|
25
|
+
|
|
26
|
+
async def main() -> None:
|
|
27
|
+
client = ExecutorClient(
|
|
28
|
+
environment_key=os.environ["BMA_ENVIRONMENT_KEY"],
|
|
29
|
+
environment_id=os.environ["BMA_ENVIRONMENT_ID"],
|
|
30
|
+
executor={"name": "my-executor", "os": "linux", "arch": "arm64", "version": "0.1.0"},
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
async def on_tool_call(call):
|
|
34
|
+
return {"session_id": call["session_id"], "tool_call_id": call["tool_call_id"],
|
|
35
|
+
"outcome": "succeeded", "is_error": False, "content": [{"type": "text", "text": "ok"}]}
|
|
36
|
+
|
|
37
|
+
client.on_tool_call(on_tool_call)
|
|
38
|
+
await client.start()
|
|
39
|
+
await asyncio.Event().wait()
|
|
40
|
+
|
|
41
|
+
asyncio.run(main())
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
A complete version that runs bash and stops the process on cancellation is [`examples/executor_minimal.py`](examples/executor_minimal.py); set `BMA_HARNESS_URL` (`ws://…` or `unix:///path.sock`) to connect it to a local harness instead of BMA.
|
|
45
|
+
|
|
46
|
+
Behavior matches the Go SDK:
|
|
47
|
+
|
|
48
|
+
- `tool.cancel` cancels the handler's task and reports `outcome: "canceled"`; duplicate `tool.call` ids run once.
|
|
49
|
+
- `tool.result` retries `-32009` / `-32004` / `retryable` errors and hands a lost result to `on_tool_result_lost`.
|
|
50
|
+
- Reconnect resumes with `resume.executor_id` / `active_sessions`; close codes 1008, 4002, 4003 and 4004 stop the client (4001 and 4005 reconnect).
|
|
51
|
+
- Call `session_ready` after setup and `session_ended` after cleanup. `on_request(method, handler)` serves `process.*`, `terminal.*` or `skill.install` yourself; there is no execution `Core` in Python.
|
|
52
|
+
|
|
53
|
+
## Harness quick start
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from bma_executor_sdk import Harness
|
|
57
|
+
|
|
58
|
+
harness = Harness(authenticate=authenticate) # authenticate(ConnectRequest) -> Principal; omit only in-process or on a private socket
|
|
59
|
+
server = await harness.serve("127.0.0.1", 0) # or await harness.serve_unix(path)
|
|
60
|
+
|
|
61
|
+
executor = await harness.wait_executor()
|
|
62
|
+
session = await executor.assign_session({"session_id": "ses_1"})
|
|
63
|
+
await session.ready()
|
|
64
|
+
result = await session.call_tool({"tool": "bash", "input": {"command": "ls"}})
|
|
65
|
+
mcp = await session.mcp("echo") # raw MCP JSON-RPC
|
|
66
|
+
await session.stop("done")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Semantics match the Go `Harness`: `call_tool` is idempotent by `tool_call_id`; sessions and pending calls survive a reconnect within the lease; lease expiry fails pending calls with `ToolOutcomeUnknownError`.
|
|
70
|
+
|
|
71
|
+
## Transports
|
|
72
|
+
|
|
73
|
+
Chosen by the channel URL of the token source:
|
|
74
|
+
|
|
75
|
+
| Transport | Executor | Harness |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| WebSocket (`ws://`, `wss://`) | default | `await harness.serve(host, port)` |
|
|
78
|
+
| WebSocket over a Unix domain socket (`unix:///path.sock`) | `token_source=static_token("unix:///path.sock", token)` | `await harness.serve_unix(path)` |
|
|
79
|
+
| In process | `dialer=harness.inprocess_dialer()` | — |
|
|
80
|
+
|
|
81
|
+
The executor-listens local socket and Windows named pipes are not implemented in Python (use the Go or TypeScript SDK). See [local connection](../../docs/executor/local-connection.md).
|
|
82
|
+
|
|
83
|
+
## Agent executors
|
|
84
|
+
|
|
85
|
+
`client.serve_agent(handler, capability)` serves the `agent` capability; `handler` implements `open_turn`, `run_turn` and `close_session` (async; cancellation is task cancellation), and `run_turn` uses `turn.emit`, `await turn.delta(...)`, `turn.inputs` (an `asyncio.Queue`) and `turn.request_interaction`. The harness side is `Harness(agent=platform)` with an `AgentPlatform`. See [agent capability](../../docs/executor/agent-capability.md).
|
|
86
|
+
|
|
87
|
+
## Go ↔ Python
|
|
88
|
+
|
|
89
|
+
| Go (`bmaexecutor`) | Python (`bma_executor_sdk`) |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| `NewClient(Options{...})` | `ExecutorClient(environment_id=..., executor=..., ...)` |
|
|
92
|
+
| `Options.TokenSource`, `StaticToken(url, token)` | `token_source=`, `static_token(url, token)` |
|
|
93
|
+
| `Client.Start` / `Stop` / `State` / `ExecutorID` / `ConnectionID` / `ActiveSessions` | `start()` / `stop(reason)` / `state` / `executor_id` / `connection_id` / `active_sessions` |
|
|
94
|
+
| `OnToolCall` / `OnSessionAssign` / `OnSessionStop` / `OnDrain` / `OnRequest` / `OnNotification` | `on_tool_call` / `on_session_assign` / `on_session_stop` / `on_drain` / `on_request` / `on_notification` |
|
|
95
|
+
| `Call` / `Notify` / `ReportToolResult` / `SessionReady` / `SessionEnded` / `OpenMCPChannel` | `call` / `notify` / `report_tool_result` / `session_ready` / `session_ended` / `open_mcp_channel` |
|
|
96
|
+
| `ServeAgent(handler, AgentCapability)` | `serve_agent(handler, capability)` |
|
|
97
|
+
| `AgentHandler.OpenTurn` / `RunTurn` / `CloseSession` | `open_turn` / `run_turn` / `close_session` |
|
|
98
|
+
| `AgentTurn.Emit` / `EmitWithID` / `NewEventID` / `Delta` / `Flush` / `Inputs()` / `RequestInteraction` / `CancelInteraction` | `emit` / `emit_with_id` / `new_event_id` / `await delta` / `await flush` / `inputs` / `request_interaction` / `cancel_interaction` |
|
|
99
|
+
| `NewHarness(HarnessOptions{...})`, `http.Handler` | `Harness(...)`, `await serve(host, port)` / `serve_unix(path)` / `inprocess_dialer()` |
|
|
100
|
+
| `HarnessOptions.Authenticate(*http.Request) (Principal, error)` | `authenticate(ConnectRequest) -> Principal` (raise to refuse) |
|
|
101
|
+
| `Harness.Executors` / `WaitExecutor` | `executors()` / `wait_executor()` |
|
|
102
|
+
| `Executor.AssignSession` / `Drain` / `Capabilities` / `Protocol` | `assign_session` / `drain` / `capabilities` / `protocol` |
|
|
103
|
+
| `Executor.StartTurn` / `SendTurnInput` / `CancelTurn` / `CloseAgentSession` / `RespondInteraction` / `AgentCapability` | `start_turn` / `send_turn_input` / `cancel_turn` / `close_agent_session` / `respond_interaction` / `agent_capability()` |
|
|
104
|
+
| `Session.Ready` / `CallTool` / `CancelTool` / `Stop` / `MCP` / `Done` / `EndReason` | `ready()` / `call_tool` / `cancel_tool` / `stop` / `mcp` / `wait_ended()`, `ended` / `end_reason` |
|
|
105
|
+
| `AgentPlatform` | `AgentPlatform` (Protocol: `acked_sequence`, `agent_event`, `agent_delta`, `interaction_requested`, `interaction_canceled`) |
|
|
106
|
+
| `ErrNotConnected` / `ErrClientStopped` / `ErrSessionEnded` / `ErrToolOutcomeUnknown` / `ErrAssignRejected` / `*RPCError` | `NotConnectedError` / `ClientStoppedError` / `SessionEndedError` / `ToolOutcomeUnknownError` / `AssignRejectedError` / `RPCError` |
|
|
107
|
+
| `Core`, `NewProcessCapability`, `ListenUnixSocket`, `Harness.DialExecutor` | not provided |
|
|
108
|
+
|
|
109
|
+
## Examples
|
|
110
|
+
|
|
111
|
+
Run from [`examples`](examples) after `uv sync` (or `pip install bma-executor-sdk`):
|
|
112
|
+
|
|
113
|
+
| Example | What it shows |
|
|
114
|
+
| --- | --- |
|
|
115
|
+
| [`executor_minimal.py`](examples/executor_minimal.py) | executor answering bash calls, against BMA or a local harness |
|
|
116
|
+
| [`harness_loopback.py`](examples/harness_loopback.py) | brain and hand in one process, in-process or `--unix PATH` |
|
|
117
|
+
| [`agent_minimal.py`](examples/agent_minimal.py) | agent capability with an in-memory platform |
|
|
118
|
+
|
|
119
|
+
## Tests
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
uv sync && uv run pytest -q
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
[`tests/test_executor_interop.py`](tests/test_executor_interop.py) runs the Python executor against the Go harness (WebSocket and Unix socket) and the Python harness against the Go executor; it needs Go.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "bma-executor-sdk"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.2"
|
|
4
4
|
description = "BMA executor protocol (bma.executor.v1.1): executor, harness and agent ends on asyncio with WebSocket, Unix socket and in-process transports."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -28,6 +28,7 @@ from .generated import (
|
|
|
28
28
|
METHOD_TOOL_CALL,
|
|
29
29
|
METHOD_TOOL_CANCEL,
|
|
30
30
|
METHOD_TOOL_RESULT,
|
|
31
|
+
METHODS,
|
|
31
32
|
Ack,
|
|
32
33
|
Capabilities,
|
|
33
34
|
ChannelScope,
|
|
@@ -509,7 +510,7 @@ class ExecutorClient:
|
|
|
509
510
|
return await self._agent.methods[method](params)
|
|
510
511
|
handler = self._request_handlers.get(method)
|
|
511
512
|
if handler is None:
|
|
512
|
-
raise
|
|
513
|
+
raise _unregistered_method_error(method)
|
|
513
514
|
return await handler(params)
|
|
514
515
|
|
|
515
516
|
def _dispatch_notification(self, method: str, params: Any) -> None:
|
|
@@ -673,3 +674,13 @@ __all__ = [
|
|
|
673
674
|
"MAX_FRAME_BYTES",
|
|
674
675
|
"static_token",
|
|
675
676
|
]
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
def _unregistered_method_error(method: str) -> RPCError:
|
|
680
|
+
"""A contract method behind a capability this executor did not register is
|
|
681
|
+
capability_unavailable naming that capability; anything else is
|
|
682
|
+
method_not_found."""
|
|
683
|
+
spec = METHODS.get(method)
|
|
684
|
+
if spec is None or not spec.capability or spec.direction == "executor-to-harness":
|
|
685
|
+
return RPCError(JsonRpcErrorCode.MethodNotFound, f"unknown method {method}")
|
|
686
|
+
return RPCError(JsonRpcErrorCode.CapabilityUnavailable, "capability_unavailable", {"capability": spec.capability})
|
|
@@ -407,3 +407,20 @@ async def test_start_turn_requires_agent_capability() -> None:
|
|
|
407
407
|
await executor.start_turn(turn_start("s", "t", "hi")) # type: ignore[arg-type]
|
|
408
408
|
assert error.value.code == JsonRpcErrorCode.CapabilityUnavailable
|
|
409
409
|
await client.stop()
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
async def test_unregistered_contract_method_is_capability_unavailable() -> None:
|
|
413
|
+
harness = bx.Harness()
|
|
414
|
+
async with listen(harness, "inprocess") as (url, dialer):
|
|
415
|
+
client = make_client(url, dialer)
|
|
416
|
+
await client.start()
|
|
417
|
+
executor = await harness.wait_executor()
|
|
418
|
+
for method, capability in [("terminal.open", "terminal.enabled"), ("skill.install", "skills.install"), ("workspace.backup", "workspace.backup")]:
|
|
419
|
+
with pytest.raises(bx.RPCError) as error:
|
|
420
|
+
await executor._call(method, {}) # noqa: SLF001
|
|
421
|
+
assert error.value.code == JsonRpcErrorCode.CapabilityUnavailable, method
|
|
422
|
+
assert error.value.data == {"capability": capability}, method
|
|
423
|
+
with pytest.raises(bx.RPCError) as unknown:
|
|
424
|
+
await executor._call("vendor.unknown", {}) # noqa: SLF001
|
|
425
|
+
assert unknown.value.code == JsonRpcErrorCode.MethodNotFound
|
|
426
|
+
await client.stop()
|
bma_executor_sdk-0.4.0/PKG-INFO
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: bma-executor-sdk
|
|
3
|
-
Version: 0.4.0
|
|
4
|
-
Summary: BMA executor protocol (bma.executor.v1.1): executor, harness and agent ends on asyncio with WebSocket, Unix socket and in-process transports.
|
|
5
|
-
Project-URL: Repository, https://github.com/beeos-ai/bma-sdks
|
|
6
|
-
License-Expression: Apache-2.0
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Requires-Dist: httpx<1,>=0.27
|
|
9
|
-
Requires-Dist: websockets<17,>=14
|
|
10
|
-
Description-Content-Type: text/markdown
|
|
11
|
-
|
|
12
|
-
# bma-executor-sdk (Python)
|
|
13
|
-
|
|
14
|
-
asyncio executor (`ExecutorClient`), harness (`Harness`) and agent ends of the
|
|
15
|
-
BMA executor protocol `bma.executor.v1.1`, with TypedDict protocol types
|
|
16
|
-
generated from the BMA contract (`contracts/bma/executor`). Transports:
|
|
17
|
-
WebSocket, WebSocket over a Unix domain socket (`unix://`), in-process.
|
|
18
|
-
|
|
19
|
-
```sh
|
|
20
|
-
pip install bma-executor-sdk
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
```python
|
|
24
|
-
import bma_executor_sdk as bx
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
See the [repository README](../../README.md#python-executor) and
|
|
28
|
-
[examples](examples). The REST/façade client is the separate `bma-sdk` package.
|
bma_executor_sdk-0.4.0/README.md
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# bma-executor-sdk (Python)
|
|
2
|
-
|
|
3
|
-
asyncio executor (`ExecutorClient`), harness (`Harness`) and agent ends of the
|
|
4
|
-
BMA executor protocol `bma.executor.v1.1`, with TypedDict protocol types
|
|
5
|
-
generated from the BMA contract (`contracts/bma/executor`). Transports:
|
|
6
|
-
WebSocket, WebSocket over a Unix domain socket (`unix://`), in-process.
|
|
7
|
-
|
|
8
|
-
```sh
|
|
9
|
-
pip install bma-executor-sdk
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
```python
|
|
13
|
-
import bma_executor_sdk as bx
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
See the [repository README](../../README.md#python-executor) and
|
|
17
|
-
[examples](examples). The REST/façade client is the separate `bma-sdk` package.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|