agentenv-framework-protocol 0.1.374__tar.gz → 0.1.376__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.
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/PKG-INFO +174 -1
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/README.md +168 -0
- agentenv_framework_protocol-0.1.376/examples/computer_x11/Dockerfile +19 -0
- agentenv_framework_protocol-0.1.376/examples/computer_x11/README.md +60 -0
- agentenv_framework_protocol-0.1.376/examples/computer_x11/driver.py +374 -0
- agentenv_framework_protocol-0.1.376/examples/computer_x11/fixture.py +173 -0
- agentenv_framework_protocol-0.1.376/examples/computer_x11/start.sh +9 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/pyproject.toml +8 -1
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/__init__.py +5 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/agent_env_environment.py +390 -50
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/client.py +26 -2
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/__init__.py +67 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/card.py +105 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/checks.py +42 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/dataplane.py +116 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/driver.py +120 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/errors.py +71 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/extensions.py +131 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/frames.py +163 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/keys.py +66 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/linux.py +100 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/operations.py +183 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/platform.py +76 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/queue.py +51 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/results.py +94 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/runtime.py +226 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/settings.py +43 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/spaces.py +49 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/computer/tools.py +423 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/errors.py +29 -0
- agentenv_framework_protocol-0.1.376/src/agentenv_protocol/tool_access.py +98 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/types.py +3 -3
- agentenv_framework_protocol-0.1.376/tests/computer_fake.py +218 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_agent_env_environment.py +22 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_client.py +26 -1
- agentenv_framework_protocol-0.1.376/tests/test_computer_dataplane.py +153 -0
- agentenv_framework_protocol-0.1.376/tests/test_computer_driver.py +386 -0
- agentenv_framework_protocol-0.1.376/tests/test_computer_extensions.py +196 -0
- agentenv_framework_protocol-0.1.376/tests/test_computer_frames.py +157 -0
- agentenv_framework_protocol-0.1.376/tests/test_computer_queue.py +477 -0
- agentenv_framework_protocol-0.1.376/tests/test_computer_spaces.py +119 -0
- agentenv_framework_protocol-0.1.376/tests/test_computer_x11_example.py +290 -0
- agentenv_framework_protocol-0.1.376/tests/test_errors.py +158 -0
- agentenv_framework_protocol-0.1.376/tests/test_extension_methods.py +236 -0
- agentenv_framework_protocol-0.1.376/tests/test_tool_access.py +195 -0
- agentenv_framework_protocol-0.1.376/tests/test_tool_annotations.py +211 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/.gitignore +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/LICENSE +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/NOTICE +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/THIRD_PARTY_NOTICES.md +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/examples/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/examples/advanced_agent.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/examples/basic_agent.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/examples/custom_extensions_agent.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/examples/multimodal_agent.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/examples/reference_model.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/examples/streaming_agent.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/a2a_agent/extensions.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/a2a_agent/framework.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/a2a_agent/peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/a2a_agent/proxy.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/preflight.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/src/agentenv_protocol/transfers.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_a2a_agent.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_a2a_agent_examples.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_a2a_object_transfers.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_a2a_peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_a2a_proxy.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_a2a_staging.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_a2a_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_data_objects.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_intake.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_manifest.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_preflight.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_tool_context_fastmcp.py +0 -0
- {agentenv_framework_protocol-0.1.374 → agentenv_framework_protocol-0.1.376}/tests/test_tool_context_injection.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: agentenv-framework-protocol
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.376
|
|
4
4
|
Summary: Open data-plane protocol and server SDK for agent environments.
|
|
5
5
|
Project-URL: Homepage, https://www.agentenvframework.com
|
|
6
6
|
Project-URL: Documentation, https://www.agentenvframework.com/docs
|
|
@@ -34,10 +34,15 @@ Requires-Dist: mcp<2,>=1.25; extra == 'agent'
|
|
|
34
34
|
Requires-Dist: python-multipart>=0.0.13; extra == 'agent'
|
|
35
35
|
Requires-Dist: regex>=2023.12.25; extra == 'agent'
|
|
36
36
|
Requires-Dist: uvicorn>=0.30; extra == 'agent'
|
|
37
|
+
Provides-Extra: computer
|
|
38
|
+
Requires-Dist: mcp<2,>=1.25; extra == 'computer'
|
|
39
|
+
Requires-Dist: pillow>=10.4; extra == 'computer'
|
|
40
|
+
Requires-Dist: uvicorn>=0.30; extra == 'computer'
|
|
37
41
|
Provides-Extra: dev
|
|
38
42
|
Requires-Dist: a2a-sdk[http-server]<0.4,>=0.3.26; extra == 'dev'
|
|
39
43
|
Requires-Dist: fastmcp<4,>=3.4; extra == 'dev'
|
|
40
44
|
Requires-Dist: mcp<2,>=1.25; extra == 'dev'
|
|
45
|
+
Requires-Dist: pillow>=10.4; extra == 'dev'
|
|
41
46
|
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
42
47
|
Requires-Dist: pytest>=9.0; extra == 'dev'
|
|
43
48
|
Requires-Dist: python-multipart>=0.0.13; extra == 'dev'
|
|
@@ -136,10 +141,34 @@ step names it. Steps that name it show and hide it as they do any tool. Steps fo
|
|
|
136
141
|
that role's earlier named steps, and a full reset (`role: "*", tools: "*"`) clears all of them, so
|
|
137
142
|
grant a hidden tool after such steps. Trigger tool actions call it as usual.
|
|
138
143
|
|
|
144
|
+
`@tool(..., annotations={"readOnlyHint": True}, structured_output=True, meta={...})` lists the tool
|
|
145
|
+
with MCP `ToolAnnotations`, an output schema and `_meta`, on mcp's FastMCP and on fastmcp alike.
|
|
146
|
+
`structured_output` follows mcp's FastMCP (None derives an output schema from the return annotation
|
|
147
|
+
where it can, False lists none); a tool returning `Annotated[CallToolResult, Model]` sends its
|
|
148
|
+
result as is, image blocks included, and is listed with `Model`'s schema. An environment chooses
|
|
149
|
+
at mount which of its `@tool` methods it serves, and with which descriptions, by overriding
|
|
150
|
+
`served_tools(tools)`. After the mount, `env.mounted_card()` is the card the application serves, and
|
|
151
|
+
`await env.update_tool(name, description=...)` changes a served tool's description on it and in
|
|
152
|
+
`tools/list` alike, on both FastMCPs.
|
|
153
|
+
|
|
139
154
|
`AgentEnvStarletteApplication` mounts the same handler onto a Starlette/FastAPI app instead of
|
|
140
155
|
FastMCP; since those apps have no MCP tool registry, constructing one with `@tool` methods
|
|
141
156
|
raises.
|
|
142
157
|
|
|
158
|
+
### Errors
|
|
159
|
+
|
|
160
|
+
Raise `AgentEnvError(code, message, *, retryable=False, applied="no", hint=None, status=400)` to
|
|
161
|
+
answer a caller with a code rather than a crash. From an `@extension` method it answers with
|
|
162
|
+
`status` and the body `{"ok": false, "error": {code, message, retryable, applied, hint?}}`; from a
|
|
163
|
+
`@tool` it answers `isError` with `"<code>: <message>"` as the text and the same fields as the
|
|
164
|
+
structured content. `applied` says whether the call's effect happened (`yes`, `no`, or `unknown`
|
|
165
|
+
when it may have been delivered before something failed), and callers retry on their own only when
|
|
166
|
+
`retryable` is true and `applied` is `no`. Any other exception in an extension still answers
|
|
167
|
+
`extension_failed` with status 500, now with `applied: "unknown"`. On the client side,
|
|
168
|
+
`invoke_extension` raises `ExtensionCallError`, an `httpx.HTTPStatusError` carrying `code`,
|
|
169
|
+
`detail`, `retryable`, `applied` and `hint`; a body without those fields, such as a proxy's error
|
|
170
|
+
page, leaves `code` None and `applied` `unknown`.
|
|
171
|
+
|
|
143
172
|
### Who is calling
|
|
144
173
|
|
|
145
174
|
A tool that needs the caller declares a parameter annotated `ToolContext` (any name) and receives
|
|
@@ -226,6 +255,40 @@ extension's, and `client.invoke_extension(base_url, card, uri, params, method=..
|
|
|
226
255
|
its HTTP verb. Without `method`, `invoke_extension` calls the first method listed; an extension
|
|
227
256
|
with several methods, such as a gateway's `urn:agentenv:clock/v1`, should always be called by name.
|
|
228
257
|
|
|
258
|
+
An extension with several methods is served by giving each handler an `op`. Handlers sharing a URI
|
|
259
|
+
merge into one card entry, each method under `params.methods.<op>` with its own verb and endpoint,
|
|
260
|
+
by default `/agentenv/ext/<extension>/<op>`, where `<extension>` is the URN's name without
|
|
261
|
+
`urn:agentenv:` and the version. Extension-level params, such as features and limits, come from the
|
|
262
|
+
environment's `extension_params(uri)`, merged beside `methods`. A declaration-only extension, a
|
|
263
|
+
promise with params and no handler like `intake/v1`, is returned from `declared_extensions()`.
|
|
264
|
+
Two handlers for one method, or a handler without `op` on a URI another handler also serves, are
|
|
265
|
+
refused at mount.
|
|
266
|
+
|
|
267
|
+
An environment chooses at mount which of its `@extension` handlers it serves by overriding
|
|
268
|
+
`served_extensions(extensions)`: an extension it leaves out is not served, and of one served with
|
|
269
|
+
`op`, only the methods its returned descriptor still lists under `params.methods`.
|
|
270
|
+
`served_operations(operations)` chooses the data-plane methods (`data/add` and the others it
|
|
271
|
+
marks) the same way. Either hook returning something with no handler is refused at mount.
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
@extension("urn:agentenv:computer-display/v1", op="get", method="GET")
|
|
275
|
+
async def display_get(self) -> dict: ...
|
|
276
|
+
|
|
277
|
+
@extension("urn:agentenv:computer-display/v1", op="set")
|
|
278
|
+
async def display_set(self, width: int, height: int) -> dict: ...
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Tool access without a gateway
|
|
282
|
+
|
|
283
|
+
An environment agents reach directly, with no gateway in front, can enforce per-role tool access
|
|
284
|
+
itself: set `tool_access = True` on the class. It then filters `tools/list` and refuses `tools/call`
|
|
285
|
+
(`permission_denied`) by the caller's role, read from `AgentEnv-Role` or a gateway-stamped `_meta`
|
|
286
|
+
role, and serves `urn:agentenv:disable-tool/v1` and `urn:agentenv:enable-tool/v1` at the gateway's
|
|
287
|
+
paths (`POST /tools/disable`, `POST /tools/enable`) with its bodies (`{role, tools: [...] | "*"}` to
|
|
288
|
+
`{role, disabled, allowed}`) and its rules, hidden-by-default tools included. So agent-env's
|
|
289
|
+
`modify_env_tool_access` step works on such an env unchanged. A call with no role is `default`, as
|
|
290
|
+
at the gateway.
|
|
291
|
+
|
|
229
292
|
Where the binding lives depends on the app. On mcp's own FastMCP it wraps the tool manager's dispatch; on the standalone `fastmcp` package it joins the app's middleware chain as the outermost layer, so a handler's own middleware runs under it too. An app with neither seam mounts unbound, with a warning, unless a tool declares a `ToolContext` parameter or the handler defines `on_tool_call`: then the mount is refused rather than serving an empty caller.
|
|
230
293
|
|
|
231
294
|
### Uploading an export through a grant
|
|
@@ -300,6 +363,116 @@ async with ToolSession(mcp_url=mcp_url, role="alice@example.com") as session:
|
|
|
300
363
|
- **Tools:** `list_tools()` returns the tools the role may use as `EnvironmentTool`, the type a card's
|
|
301
364
|
`capabilities.tools` holds. Each `inputSchema` is the JSON Schema a model API takes as the tool's parameters.
|
|
302
365
|
|
|
366
|
+
## Computer drivers
|
|
367
|
+
|
|
368
|
+
A computer driver is what runs inside a computer an agent uses through screenshots and input. Write
|
|
369
|
+
one as a `ComputerDriverEnvironment` subclass from `agentenv_protocol.computer`, which needs the
|
|
370
|
+
`computer` extra (`pip install 'agentenv-framework-protocol[computer]'`: mcp, uvicorn and Pillow).
|
|
371
|
+
The driver marks the methods that capture the screen and send input; the base class serves the
|
|
372
|
+
computer contract from them, so the tools, results and errors can't drift from it. A complete
|
|
373
|
+
driver for an X11 desktop, with an image to run it in, is in
|
|
374
|
+
[`examples/computer_x11/`](https://github.com/scaleapi/agentenv-framework/tree/main/packages/agentenv-protocol/examples/computer_x11).
|
|
375
|
+
|
|
376
|
+
```python
|
|
377
|
+
from agentenv_protocol.computer import (
|
|
378
|
+
ComputerDriverEnvironment, Display, computer_driver,
|
|
379
|
+
capture, click, display, drag, press, scroll, type_text,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
@computer_driver(name="my-driver", supports=[{"os": {"type": "linux", "display": ["x11"]}, "arch": ["amd64"]}])
|
|
384
|
+
class MyDriver(ComputerDriverEnvironment):
|
|
385
|
+
buttons = ("left", "right") # features, on the card
|
|
386
|
+
max_click_count = 2
|
|
387
|
+
|
|
388
|
+
@display
|
|
389
|
+
async def size(self) -> Display: ... # width, height, scale
|
|
390
|
+
@capture(display="x11")
|
|
391
|
+
async def grab(self): ... # the whole display, native resolution
|
|
392
|
+
@click
|
|
393
|
+
async def click_at(self, x, y, button, count, modifiers): ...
|
|
394
|
+
@drag
|
|
395
|
+
async def drag_along(self, path, button, duration_ms, modifiers): ...
|
|
396
|
+
@scroll
|
|
397
|
+
async def scroll_at(self, x, y, direction, amount, modifiers): ...
|
|
398
|
+
@type_text
|
|
399
|
+
async def type_in(self, text): ...
|
|
400
|
+
@press
|
|
401
|
+
async def press_keys(self, keys): ...
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
if __name__ == "__main__":
|
|
405
|
+
MyDriver().serve()
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**Operations.** Operations get points in screen pixels, after the base class has mapped, checked
|
|
409
|
+
and queued the call, and may be sync or async. `@capture` returns a Pillow image or an
|
|
410
|
+
`Image(width, height, data, mode)` (PNG or JPEG bytes, or raw `RGB`, `RGBA`, `BGRA` or `BGRX`
|
|
411
|
+
pixels), at the display's size times its scale. Input operations may return an `Effect`.
|
|
412
|
+
|
|
413
|
+
| Decorator | Required | Gives |
|
|
414
|
+
|---|---|---|
|
|
415
|
+
| `@display`, `@capture`, `@click`, `@drag`, `@scroll`, `@type_text`, `@press` | yes | the seven `computer_*` tools and `computer/v1` |
|
|
416
|
+
| `@set_display` | no, with `display_modes` | `computer-display/v1` `set` (`get` is always served) |
|
|
417
|
+
| `@pointer_move`, `@pointer_down`, `@pointer_up`, `@pointer_position` | all or none | `computer-pointer/v1` and its five tools; click and scroll at the pointer |
|
|
418
|
+
| `@key_down`, `@key_up` | both or none | `computer-keyboard/v1` and its two tools |
|
|
419
|
+
| `@run_in_session` | no; a Linux default | `computer-exec/v1` |
|
|
420
|
+
| `@write_files` | no; a Linux default | `data/add` |
|
|
421
|
+
|
|
422
|
+
The class is checked when it is defined: a missing required operation, half a group, two methods
|
|
423
|
+
for one operation that some platform could both match, or features that don't match the
|
|
424
|
+
decorators raise `TypeError`. Predicates (`os`, `distribution`, `version`, `arch`, `display`)
|
|
425
|
+
choose a method per platform, read from `/etc/os-release`, the machine and `XDG_SESSION_TYPE` or
|
|
426
|
+
`DISPLAY`: `@capture(display="x11")` and `@capture(display="wayland")` can sit side by side. A
|
|
427
|
+
driver's own `@run_in_session` or `@write_files` replaces the base class's Linux default, which runs
|
|
428
|
+
as the driver's user inside the desktop session. The base class keeps its state in `self._computer`
|
|
429
|
+
and names its own members `_computer_*` and `computer_*`; a driver that redefines one of them, or
|
|
430
|
+
another private member of the base class, is refused when it is defined, so a helper of the
|
|
431
|
+
driver's can't silently replace one. What the optional operations give is served only when the
|
|
432
|
+
driver has them, through the `served_tools`, `served_extensions` and `served_operations` hooks.
|
|
433
|
+
|
|
434
|
+
**What the base class serves:**
|
|
435
|
+
|
|
436
|
+
- **Tools.** `computer_screenshot` (a frame: `frame_id`, image size, display resolution and scale;
|
|
437
|
+
`region` crops a frame at native resolution), `computer_click`, `computer_drag`,
|
|
438
|
+
`computer_scroll`, `computer_type`, `computer_key` (W3C key names) and `computer_wait`, plus the
|
|
439
|
+
group tools. Every result is structured, with a one-line summary as its text block; input results
|
|
440
|
+
carry `applied`, `delivery` and `others_since_frame`, and `screenshot: true` returns a fresh frame
|
|
441
|
+
after acting. Click and scroll leave out `x` and `y` to act at the pointer on a computer with
|
|
442
|
+
`computer-pointer/v1`; elsewhere a call without them is refused `invalid_argument`.
|
|
443
|
+
- **Frames and spaces.** Points are pixels of a named frame (your latest by default), or a
|
|
444
|
+
0..`norm_scale` grid over it with `space: "norm"`. A point outside the frame or the range is
|
|
445
|
+
refused (`out_of_bounds`), never clamped. The last 64 frames are kept, and a frame goes stale
|
|
446
|
+
(`stale_frame`) when the display geometry it was captured at changes.
|
|
447
|
+
- **The input queue.** Input calls run one at a time in arrival order; one that waits past
|
|
448
|
+
`queue_wait_s` is refused `input_busy`. Screenshots and waits never queue.
|
|
449
|
+
- **Framework extensions.** `computer-ready/v1` (`status`: real checks of the display and a
|
|
450
|
+
capture, plus the driver's own `ready_checks()`; tools refuse `device_unavailable` until they
|
|
451
|
+
pass once), `computer-display/v1`, `computer-spaces/v1` (the default space, written into the tool
|
|
452
|
+
descriptions), `computer-keyboard/v1` and `computer-exec/v1` (`run {argv, cwd?, env?, timeout_s?,
|
|
453
|
+
request_id}`: output capped per stream, a repeated `request_id` answered with the first result).
|
|
454
|
+
- **The data plane.** `data/add` takes file parts that name their path, relative to the session
|
|
455
|
+
user's home, under the `data-objects/v1` marker (`{"path": ...}`), as inline bytes, an https URL,
|
|
456
|
+
or a `transfers.ReadObject` read grant under the marker's `read`, and writes them atomically.
|
|
457
|
+
`data/get` returns the files under the settings' `data_paths`, inline or uploaded under a
|
|
458
|
+
`write_namespace` grant.
|
|
459
|
+
- **The card** (the name from `ENVIRONMENT_NAME`, then `@computer_driver`'s), and per-role tool
|
|
460
|
+
access (`tool_access` is on).
|
|
461
|
+
|
|
462
|
+
**Errors.** A driver raises the errors table's exceptions, all `AgentEnvError`s:
|
|
463
|
+
`InvalidArgument`, `OutOfBounds`, `StaleFrame`, `InputBusy`, `PermissionDenied`, `Unsupported`,
|
|
464
|
+
`BackgroundUnavailable`, `TargetUnavailable`, `DeviceUnavailable` and `LimitExceeded` mean nothing
|
|
465
|
+
was sent (`applied: "no"`); `Timeout`, `DeliveryFailed` and `Internal` mean it may have been. Any
|
|
466
|
+
other exception from an input operation is reported `delivery_failed` with `applied: "unknown"`.
|
|
467
|
+
|
|
468
|
+
**Settings** reach the driver as one JSON value in `AGENTENV_COMPUTER_SETTINGS`
|
|
469
|
+
(`ComputerSettings`): `data_paths`, `default_space`, `default_norm_scale`, `queue_wait_s`,
|
|
470
|
+
`tap_hold_ms`, `type_delay_ms`, `max_hold_ms`, `layout`, `drag_duration_ms`, `exec_output_bytes`
|
|
471
|
+
and `exec_timeout_s`. A driver reads them as `self.settings`, for example its tap hold in `@press`.
|
|
472
|
+
|
|
473
|
+
Every caller is `default` until agents carry an identity of their own; `caller_identity(context)`
|
|
474
|
+
names the caller whose latest frame and others' input a call uses.
|
|
475
|
+
|
|
303
476
|
## A2A agent framework
|
|
304
477
|
|
|
305
478
|
The distribution exposes two unrelated decorators named `extension`:
|
|
@@ -90,10 +90,34 @@ step names it. Steps that name it show and hide it as they do any tool. Steps fo
|
|
|
90
90
|
that role's earlier named steps, and a full reset (`role: "*", tools: "*"`) clears all of them, so
|
|
91
91
|
grant a hidden tool after such steps. Trigger tool actions call it as usual.
|
|
92
92
|
|
|
93
|
+
`@tool(..., annotations={"readOnlyHint": True}, structured_output=True, meta={...})` lists the tool
|
|
94
|
+
with MCP `ToolAnnotations`, an output schema and `_meta`, on mcp's FastMCP and on fastmcp alike.
|
|
95
|
+
`structured_output` follows mcp's FastMCP (None derives an output schema from the return annotation
|
|
96
|
+
where it can, False lists none); a tool returning `Annotated[CallToolResult, Model]` sends its
|
|
97
|
+
result as is, image blocks included, and is listed with `Model`'s schema. An environment chooses
|
|
98
|
+
at mount which of its `@tool` methods it serves, and with which descriptions, by overriding
|
|
99
|
+
`served_tools(tools)`. After the mount, `env.mounted_card()` is the card the application serves, and
|
|
100
|
+
`await env.update_tool(name, description=...)` changes a served tool's description on it and in
|
|
101
|
+
`tools/list` alike, on both FastMCPs.
|
|
102
|
+
|
|
93
103
|
`AgentEnvStarletteApplication` mounts the same handler onto a Starlette/FastAPI app instead of
|
|
94
104
|
FastMCP; since those apps have no MCP tool registry, constructing one with `@tool` methods
|
|
95
105
|
raises.
|
|
96
106
|
|
|
107
|
+
### Errors
|
|
108
|
+
|
|
109
|
+
Raise `AgentEnvError(code, message, *, retryable=False, applied="no", hint=None, status=400)` to
|
|
110
|
+
answer a caller with a code rather than a crash. From an `@extension` method it answers with
|
|
111
|
+
`status` and the body `{"ok": false, "error": {code, message, retryable, applied, hint?}}`; from a
|
|
112
|
+
`@tool` it answers `isError` with `"<code>: <message>"` as the text and the same fields as the
|
|
113
|
+
structured content. `applied` says whether the call's effect happened (`yes`, `no`, or `unknown`
|
|
114
|
+
when it may have been delivered before something failed), and callers retry on their own only when
|
|
115
|
+
`retryable` is true and `applied` is `no`. Any other exception in an extension still answers
|
|
116
|
+
`extension_failed` with status 500, now with `applied: "unknown"`. On the client side,
|
|
117
|
+
`invoke_extension` raises `ExtensionCallError`, an `httpx.HTTPStatusError` carrying `code`,
|
|
118
|
+
`detail`, `retryable`, `applied` and `hint`; a body without those fields, such as a proxy's error
|
|
119
|
+
page, leaves `code` None and `applied` `unknown`.
|
|
120
|
+
|
|
97
121
|
### Who is calling
|
|
98
122
|
|
|
99
123
|
A tool that needs the caller declares a parameter annotated `ToolContext` (any name) and receives
|
|
@@ -180,6 +204,40 @@ extension's, and `client.invoke_extension(base_url, card, uri, params, method=..
|
|
|
180
204
|
its HTTP verb. Without `method`, `invoke_extension` calls the first method listed; an extension
|
|
181
205
|
with several methods, such as a gateway's `urn:agentenv:clock/v1`, should always be called by name.
|
|
182
206
|
|
|
207
|
+
An extension with several methods is served by giving each handler an `op`. Handlers sharing a URI
|
|
208
|
+
merge into one card entry, each method under `params.methods.<op>` with its own verb and endpoint,
|
|
209
|
+
by default `/agentenv/ext/<extension>/<op>`, where `<extension>` is the URN's name without
|
|
210
|
+
`urn:agentenv:` and the version. Extension-level params, such as features and limits, come from the
|
|
211
|
+
environment's `extension_params(uri)`, merged beside `methods`. A declaration-only extension, a
|
|
212
|
+
promise with params and no handler like `intake/v1`, is returned from `declared_extensions()`.
|
|
213
|
+
Two handlers for one method, or a handler without `op` on a URI another handler also serves, are
|
|
214
|
+
refused at mount.
|
|
215
|
+
|
|
216
|
+
An environment chooses at mount which of its `@extension` handlers it serves by overriding
|
|
217
|
+
`served_extensions(extensions)`: an extension it leaves out is not served, and of one served with
|
|
218
|
+
`op`, only the methods its returned descriptor still lists under `params.methods`.
|
|
219
|
+
`served_operations(operations)` chooses the data-plane methods (`data/add` and the others it
|
|
220
|
+
marks) the same way. Either hook returning something with no handler is refused at mount.
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
@extension("urn:agentenv:computer-display/v1", op="get", method="GET")
|
|
224
|
+
async def display_get(self) -> dict: ...
|
|
225
|
+
|
|
226
|
+
@extension("urn:agentenv:computer-display/v1", op="set")
|
|
227
|
+
async def display_set(self, width: int, height: int) -> dict: ...
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Tool access without a gateway
|
|
231
|
+
|
|
232
|
+
An environment agents reach directly, with no gateway in front, can enforce per-role tool access
|
|
233
|
+
itself: set `tool_access = True` on the class. It then filters `tools/list` and refuses `tools/call`
|
|
234
|
+
(`permission_denied`) by the caller's role, read from `AgentEnv-Role` or a gateway-stamped `_meta`
|
|
235
|
+
role, and serves `urn:agentenv:disable-tool/v1` and `urn:agentenv:enable-tool/v1` at the gateway's
|
|
236
|
+
paths (`POST /tools/disable`, `POST /tools/enable`) with its bodies (`{role, tools: [...] | "*"}` to
|
|
237
|
+
`{role, disabled, allowed}`) and its rules, hidden-by-default tools included. So agent-env's
|
|
238
|
+
`modify_env_tool_access` step works on such an env unchanged. A call with no role is `default`, as
|
|
239
|
+
at the gateway.
|
|
240
|
+
|
|
183
241
|
Where the binding lives depends on the app. On mcp's own FastMCP it wraps the tool manager's dispatch; on the standalone `fastmcp` package it joins the app's middleware chain as the outermost layer, so a handler's own middleware runs under it too. An app with neither seam mounts unbound, with a warning, unless a tool declares a `ToolContext` parameter or the handler defines `on_tool_call`: then the mount is refused rather than serving an empty caller.
|
|
184
242
|
|
|
185
243
|
### Uploading an export through a grant
|
|
@@ -254,6 +312,116 @@ async with ToolSession(mcp_url=mcp_url, role="alice@example.com") as session:
|
|
|
254
312
|
- **Tools:** `list_tools()` returns the tools the role may use as `EnvironmentTool`, the type a card's
|
|
255
313
|
`capabilities.tools` holds. Each `inputSchema` is the JSON Schema a model API takes as the tool's parameters.
|
|
256
314
|
|
|
315
|
+
## Computer drivers
|
|
316
|
+
|
|
317
|
+
A computer driver is what runs inside a computer an agent uses through screenshots and input. Write
|
|
318
|
+
one as a `ComputerDriverEnvironment` subclass from `agentenv_protocol.computer`, which needs the
|
|
319
|
+
`computer` extra (`pip install 'agentenv-framework-protocol[computer]'`: mcp, uvicorn and Pillow).
|
|
320
|
+
The driver marks the methods that capture the screen and send input; the base class serves the
|
|
321
|
+
computer contract from them, so the tools, results and errors can't drift from it. A complete
|
|
322
|
+
driver for an X11 desktop, with an image to run it in, is in
|
|
323
|
+
[`examples/computer_x11/`](https://github.com/scaleapi/agentenv-framework/tree/main/packages/agentenv-protocol/examples/computer_x11).
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
from agentenv_protocol.computer import (
|
|
327
|
+
ComputerDriverEnvironment, Display, computer_driver,
|
|
328
|
+
capture, click, display, drag, press, scroll, type_text,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@computer_driver(name="my-driver", supports=[{"os": {"type": "linux", "display": ["x11"]}, "arch": ["amd64"]}])
|
|
333
|
+
class MyDriver(ComputerDriverEnvironment):
|
|
334
|
+
buttons = ("left", "right") # features, on the card
|
|
335
|
+
max_click_count = 2
|
|
336
|
+
|
|
337
|
+
@display
|
|
338
|
+
async def size(self) -> Display: ... # width, height, scale
|
|
339
|
+
@capture(display="x11")
|
|
340
|
+
async def grab(self): ... # the whole display, native resolution
|
|
341
|
+
@click
|
|
342
|
+
async def click_at(self, x, y, button, count, modifiers): ...
|
|
343
|
+
@drag
|
|
344
|
+
async def drag_along(self, path, button, duration_ms, modifiers): ...
|
|
345
|
+
@scroll
|
|
346
|
+
async def scroll_at(self, x, y, direction, amount, modifiers): ...
|
|
347
|
+
@type_text
|
|
348
|
+
async def type_in(self, text): ...
|
|
349
|
+
@press
|
|
350
|
+
async def press_keys(self, keys): ...
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
if __name__ == "__main__":
|
|
354
|
+
MyDriver().serve()
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**Operations.** Operations get points in screen pixels, after the base class has mapped, checked
|
|
358
|
+
and queued the call, and may be sync or async. `@capture` returns a Pillow image or an
|
|
359
|
+
`Image(width, height, data, mode)` (PNG or JPEG bytes, or raw `RGB`, `RGBA`, `BGRA` or `BGRX`
|
|
360
|
+
pixels), at the display's size times its scale. Input operations may return an `Effect`.
|
|
361
|
+
|
|
362
|
+
| Decorator | Required | Gives |
|
|
363
|
+
|---|---|---|
|
|
364
|
+
| `@display`, `@capture`, `@click`, `@drag`, `@scroll`, `@type_text`, `@press` | yes | the seven `computer_*` tools and `computer/v1` |
|
|
365
|
+
| `@set_display` | no, with `display_modes` | `computer-display/v1` `set` (`get` is always served) |
|
|
366
|
+
| `@pointer_move`, `@pointer_down`, `@pointer_up`, `@pointer_position` | all or none | `computer-pointer/v1` and its five tools; click and scroll at the pointer |
|
|
367
|
+
| `@key_down`, `@key_up` | both or none | `computer-keyboard/v1` and its two tools |
|
|
368
|
+
| `@run_in_session` | no; a Linux default | `computer-exec/v1` |
|
|
369
|
+
| `@write_files` | no; a Linux default | `data/add` |
|
|
370
|
+
|
|
371
|
+
The class is checked when it is defined: a missing required operation, half a group, two methods
|
|
372
|
+
for one operation that some platform could both match, or features that don't match the
|
|
373
|
+
decorators raise `TypeError`. Predicates (`os`, `distribution`, `version`, `arch`, `display`)
|
|
374
|
+
choose a method per platform, read from `/etc/os-release`, the machine and `XDG_SESSION_TYPE` or
|
|
375
|
+
`DISPLAY`: `@capture(display="x11")` and `@capture(display="wayland")` can sit side by side. A
|
|
376
|
+
driver's own `@run_in_session` or `@write_files` replaces the base class's Linux default, which runs
|
|
377
|
+
as the driver's user inside the desktop session. The base class keeps its state in `self._computer`
|
|
378
|
+
and names its own members `_computer_*` and `computer_*`; a driver that redefines one of them, or
|
|
379
|
+
another private member of the base class, is refused when it is defined, so a helper of the
|
|
380
|
+
driver's can't silently replace one. What the optional operations give is served only when the
|
|
381
|
+
driver has them, through the `served_tools`, `served_extensions` and `served_operations` hooks.
|
|
382
|
+
|
|
383
|
+
**What the base class serves:**
|
|
384
|
+
|
|
385
|
+
- **Tools.** `computer_screenshot` (a frame: `frame_id`, image size, display resolution and scale;
|
|
386
|
+
`region` crops a frame at native resolution), `computer_click`, `computer_drag`,
|
|
387
|
+
`computer_scroll`, `computer_type`, `computer_key` (W3C key names) and `computer_wait`, plus the
|
|
388
|
+
group tools. Every result is structured, with a one-line summary as its text block; input results
|
|
389
|
+
carry `applied`, `delivery` and `others_since_frame`, and `screenshot: true` returns a fresh frame
|
|
390
|
+
after acting. Click and scroll leave out `x` and `y` to act at the pointer on a computer with
|
|
391
|
+
`computer-pointer/v1`; elsewhere a call without them is refused `invalid_argument`.
|
|
392
|
+
- **Frames and spaces.** Points are pixels of a named frame (your latest by default), or a
|
|
393
|
+
0..`norm_scale` grid over it with `space: "norm"`. A point outside the frame or the range is
|
|
394
|
+
refused (`out_of_bounds`), never clamped. The last 64 frames are kept, and a frame goes stale
|
|
395
|
+
(`stale_frame`) when the display geometry it was captured at changes.
|
|
396
|
+
- **The input queue.** Input calls run one at a time in arrival order; one that waits past
|
|
397
|
+
`queue_wait_s` is refused `input_busy`. Screenshots and waits never queue.
|
|
398
|
+
- **Framework extensions.** `computer-ready/v1` (`status`: real checks of the display and a
|
|
399
|
+
capture, plus the driver's own `ready_checks()`; tools refuse `device_unavailable` until they
|
|
400
|
+
pass once), `computer-display/v1`, `computer-spaces/v1` (the default space, written into the tool
|
|
401
|
+
descriptions), `computer-keyboard/v1` and `computer-exec/v1` (`run {argv, cwd?, env?, timeout_s?,
|
|
402
|
+
request_id}`: output capped per stream, a repeated `request_id` answered with the first result).
|
|
403
|
+
- **The data plane.** `data/add` takes file parts that name their path, relative to the session
|
|
404
|
+
user's home, under the `data-objects/v1` marker (`{"path": ...}`), as inline bytes, an https URL,
|
|
405
|
+
or a `transfers.ReadObject` read grant under the marker's `read`, and writes them atomically.
|
|
406
|
+
`data/get` returns the files under the settings' `data_paths`, inline or uploaded under a
|
|
407
|
+
`write_namespace` grant.
|
|
408
|
+
- **The card** (the name from `ENVIRONMENT_NAME`, then `@computer_driver`'s), and per-role tool
|
|
409
|
+
access (`tool_access` is on).
|
|
410
|
+
|
|
411
|
+
**Errors.** A driver raises the errors table's exceptions, all `AgentEnvError`s:
|
|
412
|
+
`InvalidArgument`, `OutOfBounds`, `StaleFrame`, `InputBusy`, `PermissionDenied`, `Unsupported`,
|
|
413
|
+
`BackgroundUnavailable`, `TargetUnavailable`, `DeviceUnavailable` and `LimitExceeded` mean nothing
|
|
414
|
+
was sent (`applied: "no"`); `Timeout`, `DeliveryFailed` and `Internal` mean it may have been. Any
|
|
415
|
+
other exception from an input operation is reported `delivery_failed` with `applied: "unknown"`.
|
|
416
|
+
|
|
417
|
+
**Settings** reach the driver as one JSON value in `AGENTENV_COMPUTER_SETTINGS`
|
|
418
|
+
(`ComputerSettings`): `data_paths`, `default_space`, `default_norm_scale`, `queue_wait_s`,
|
|
419
|
+
`tap_hold_ms`, `type_delay_ms`, `max_hold_ms`, `layout`, `drag_duration_ms`, `exec_output_bytes`
|
|
420
|
+
and `exec_timeout_s`. A driver reads them as `self.settings`, for example its tap hold in `@press`.
|
|
421
|
+
|
|
422
|
+
Every caller is `default` until agents carry an identity of their own; `caller_identity(context)`
|
|
423
|
+
names the caller whose latest frame and others' input a call uses.
|
|
424
|
+
|
|
257
425
|
## A2A agent framework
|
|
258
426
|
|
|
259
427
|
The distribution exposes two unrelated decorators named `extension`:
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Built from packages/agentenv-protocol, so the driver runs on this checkout's SDK:
|
|
2
|
+
# docker build -f examples/computer_x11/Dockerfile -t x11-desktop .
|
|
3
|
+
FROM public.ecr.aws/docker/library/ubuntu:24.04
|
|
4
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
5
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
6
|
+
python3 python3-venv python3-tk xvfb x11-utils fonts-dejavu-core procps \
|
|
7
|
+
libxcb1 libxcb-render0 libxcb-shm0 libxcb-randr0 libxcb-xfixes0 libxcb-xinerama0 \
|
|
8
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
9
|
+
COPY pyproject.toml README.md LICENSE NOTICE THIRD_PARTY_NOTICES.md /opt/protocol/
|
|
10
|
+
COPY src /opt/protocol/src
|
|
11
|
+
RUN python3 -m venv /opt/driver \
|
|
12
|
+
&& /opt/driver/bin/pip install --no-cache-dir "/opt/protocol[computer]" "mss>=9,<11" "python-xlib>=0.33,<0.34"
|
|
13
|
+
RUN useradd --create-home --uid 10001 user
|
|
14
|
+
COPY examples/computer_x11/start.sh examples/computer_x11/driver.py examples/computer_x11/fixture.py /opt/desktop/
|
|
15
|
+
USER user
|
|
16
|
+
WORKDIR /home/user
|
|
17
|
+
ENV DISPLAY=:99 XDG_SESSION_TYPE=x11
|
|
18
|
+
EXPOSE 18765
|
|
19
|
+
CMD ["/bin/sh", "/opt/desktop/start.sh"]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# An X11 computer driver
|
|
2
|
+
|
|
3
|
+
[`driver.py`](driver.py) is a complete computer driver for an X11 desktop, written on
|
|
4
|
+
`ComputerDriverEnvironment` from `agentenv_protocol.computer`: `mss` captures the screen and XTEST,
|
|
5
|
+
through `python-xlib`, sends the input. It implements the seven required operations and the pointer
|
|
6
|
+
and keyboard groups, declares five buttons, triple clicks and 50-point drags, and adds a ready check
|
|
7
|
+
of its own for XTEST. The base class serves everything else from those: the `computer_*` tools,
|
|
8
|
+
frames and coordinate spaces, the input queue, the framework extensions and the card, and its Linux
|
|
9
|
+
defaults give the driver `computer-exec/v1` and `data/add`.
|
|
10
|
+
|
|
11
|
+
The image runs the driver on Xvfb beside [`fixture.py`](fixture.py), the Fixture Desk: a Tk window
|
|
12
|
+
with a name field, a checkbox, a 100-item list, a grid of cells, a drawing pad and a Save button. It
|
|
13
|
+
records whatever reaches it in `~/fixture/state.json`, so what an agent did can be read back from
|
|
14
|
+
the app rather than from the driver's answers, and shows the text of `~/inbox/message.txt`, which
|
|
15
|
+
`data/add` can write. [`start.sh`](start.sh) restarts Xvfb and the app if either dies.
|
|
16
|
+
|
|
17
|
+
## Run it
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
cd packages/agentenv-protocol
|
|
21
|
+
docker build -f examples/computer_x11/Dockerfile -t x11-desktop .
|
|
22
|
+
docker run --rm -p 18765:18765 x11-desktop
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The card is at `http://localhost:18765/.well-known/agent-env.json`, readiness at
|
|
26
|
+
`/agentenv/ext/computer-ready/status`, and the tools at the MCP endpoint `/mcp`:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import asyncio
|
|
30
|
+
|
|
31
|
+
from mcp import ClientSession
|
|
32
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def main() -> None:
|
|
36
|
+
async with streamablehttp_client("http://localhost:18765/mcp") as (read, write, _):
|
|
37
|
+
async with ClientSession(read, write) as session:
|
|
38
|
+
await session.initialize()
|
|
39
|
+
shot = await session.call_tool("computer_screenshot", {})
|
|
40
|
+
print(shot.structuredContent["summary"])
|
|
41
|
+
await session.call_tool("computer_click", {"x": 300, "y": 52})
|
|
42
|
+
typed = await session.call_tool("computer_type", {"text": "Ada Lovelace", "screenshot": True})
|
|
43
|
+
print(typed.structuredContent["summary"])
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
asyncio.run(main())
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`AGENTENV_COMPUTER_SETTINGS` takes the driver's settings as one JSON value, for example
|
|
50
|
+
`-e AGENTENV_COMPUTER_SETTINGS='{"default_space": "norm", "data_paths": ["fixture"]}'`.
|
|
51
|
+
|
|
52
|
+
## In agent-env
|
|
53
|
+
|
|
54
|
+
As a bundle env (`envs/<name>/Dockerfile`), the image is built from the env's own folder, so copy
|
|
55
|
+
the three files there and install `agentenv-framework-protocol[computer]` in place of this checkout.
|
|
56
|
+
Name the env with `environment_name` in its `env.toml`. A task then deploys it with `deploy_env`
|
|
57
|
+
and gives it to an agent with `deploy_agent`, as for any MCP env.
|
|
58
|
+
|
|
59
|
+
`tests/test_computer_x11_example.py` runs the driver against fake `mss` and `Xlib` modules, so it
|
|
60
|
+
keeps up with the SDK without an X server.
|