agentenv-framework-protocol 0.1.453__tar.gz → 0.1.456__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.453 → agentenv_framework_protocol-0.1.456}/PKG-INFO +11 -1
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/README.md +10 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/pyproject.toml +1 -1
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/client.py +80 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/_linux.py +3 -2
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/app.py +9 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/app_runtime.py +4 -2
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/conformance/checks.py +3 -1
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/driver.py +3 -3
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/platform.py +6 -4
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/probe_fake.py +6 -5
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_client.py +52 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_app_server.py +11 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_conformance.py +1 -1
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_declare.py +5 -2
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_driver.py +9 -5
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_recording.py +16 -3
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/.gitignore +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/LICENSE +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/NOTICE +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/THIRD_PARTY_NOTICES.md +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/examples/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/examples/advanced_agent.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/examples/basic_agent.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/examples/custom_extensions_agent.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/examples/multimodal_agent.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/examples/reference_model.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/examples/streaming_agent.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/a2a_agent/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/a2a_agent/_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/a2a_agent/_triggers.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/a2a_agent/extensions.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/a2a_agent/framework.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/a2a_agent/peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/a2a_agent/proxy.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/a2a_agent/registry.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/a2a_agent/staging.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/a2a_agent/tasks/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/a2a_agent/tasks/v1.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/agent_env_environment.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/browser_cdp.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/_declare.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/_load.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/_with_fds.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/app_data.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/apps.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/card.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/checks.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/conformance/__init__.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/conformance/__main__.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/conformance/client.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/conformance/fixture.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/conformance/mp4.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/conformance/page_fixture.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/conformance/report.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/dataplane.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/errors.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/extensions.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/frames.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/keys.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/launch.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/operations.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/queue.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/recording.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/results.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/run_install_step.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/runtime.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/seed.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/serve.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/serve_app.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/session.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/settings.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/skill.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/spaces.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/svc.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/computer/tools.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/errors.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/manifest.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/preflight.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/tool_access.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/transfers.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/types.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/src/agentenv_protocol/website_browser.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/computer_fake.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_a2a_agent.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_a2a_agent_examples.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_a2a_cdp_config.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_a2a_object_transfers.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_a2a_peer_agents.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_a2a_proxy.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_a2a_staging.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_a2a_trajectory_log.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_agent_env_environment.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_browser_cdp.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_app_data.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_conformance_checks.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_conformance_page.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_conformance_recording.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_dataplane.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_extensions.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_frames.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_page.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_queue.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_seed.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_serve.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_spaces.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_computer_svc_forward.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_control_token.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_data_objects.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_errors.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_extension_methods.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_intake.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_manifest.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_preflight.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_tool_access.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_tool_annotations.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_tool_client.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_tool_context.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_tool_context_fastmcp.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_tool_context_injection.py +0 -0
- {agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_website_browser.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.456
|
|
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
|
|
@@ -258,6 +258,16 @@ with any other transport are kept beside the SDK's entry. Card URLs are paths, r
|
|
|
258
258
|
address the card was fetched from. On the client side, `client.mcp_path(card)` returns the
|
|
259
259
|
declared path, or `/mcp` for a card without one.
|
|
260
260
|
|
|
261
|
+
A composed card, such as a gateway's, nests each backing environment's card under
|
|
262
|
+
`children_environments`, its `url` and endpoints rooted at the route the composer serves it at.
|
|
263
|
+
`client.children(card)` returns each as a `ChildEnv` (its `name`, `url` and `card`, with its
|
|
264
|
+
`extensions`, `operations` and `serves(method)`, and `base_url(address)`, its data plane resolved
|
|
265
|
+
against the composed card's address). Two children may share a name, as a website and an MCP
|
|
266
|
+
server can, so `client.child_at(card, url)` finds one by its url or route; `client.child_named(card,
|
|
267
|
+
name)` raises `AmbiguousChildError` for a shared name, and `client.find_child` returns the first.
|
|
268
|
+
`client.serves(card, method)` says whether a card serves a data-plane method by its declared
|
|
269
|
+
`operations`, None for a card that declares none.
|
|
270
|
+
|
|
261
271
|
Extensions are invoked from what the card advertises. `client.find_extension_method(card, uri,
|
|
262
272
|
method)` returns one advertised method, whose `endpoint` is the method's own or else the
|
|
263
273
|
extension's, and `client.invoke_extension(base_url, card, uri, params, method=...)` calls it with
|
|
@@ -199,6 +199,16 @@ with any other transport are kept beside the SDK's entry. Card URLs are paths, r
|
|
|
199
199
|
address the card was fetched from. On the client side, `client.mcp_path(card)` returns the
|
|
200
200
|
declared path, or `/mcp` for a card without one.
|
|
201
201
|
|
|
202
|
+
A composed card, such as a gateway's, nests each backing environment's card under
|
|
203
|
+
`children_environments`, its `url` and endpoints rooted at the route the composer serves it at.
|
|
204
|
+
`client.children(card)` returns each as a `ChildEnv` (its `name`, `url` and `card`, with its
|
|
205
|
+
`extensions`, `operations` and `serves(method)`, and `base_url(address)`, its data plane resolved
|
|
206
|
+
against the composed card's address). Two children may share a name, as a website and an MCP
|
|
207
|
+
server can, so `client.child_at(card, url)` finds one by its url or route; `client.child_named(card,
|
|
208
|
+
name)` raises `AmbiguousChildError` for a shared name, and `client.find_child` returns the first.
|
|
209
|
+
`client.serves(card, method)` says whether a card serves a data-plane method by its declared
|
|
210
|
+
`operations`, None for a card that declares none.
|
|
211
|
+
|
|
202
212
|
Extensions are invoked from what the card advertises. `client.find_extension_method(card, uri,
|
|
203
213
|
method)` returns one advertised method, whose `endpoint` is the method's own or else the
|
|
204
214
|
extension's, and `client.invoke_extension(base_url, card, uri, params, method=...)` calls it with
|
|
@@ -7,6 +7,7 @@ import contextlib
|
|
|
7
7
|
import logging
|
|
8
8
|
from collections.abc import Iterator
|
|
9
9
|
from contextvars import ContextVar
|
|
10
|
+
from dataclasses import dataclass, field
|
|
10
11
|
|
|
11
12
|
import httpx
|
|
12
13
|
|
|
@@ -210,6 +211,83 @@ def find_tool(card: dict, name: str) -> dict | None:
|
|
|
210
211
|
return None
|
|
211
212
|
|
|
212
213
|
|
|
214
|
+
def serves(card: dict, method: str) -> bool | None:
|
|
215
|
+
"""Whether a card serves the data-plane ``method`` (``data/get`` say): True or False by its declared
|
|
216
|
+
``capabilities.operations``, or None when it declares none, as an older card doesn't, so the caller decides as it
|
|
217
|
+
did before cards declared them (a probe, say)."""
|
|
218
|
+
declared = operations(card)
|
|
219
|
+
return None if declared is None else method in declared
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@dataclass(frozen=True)
|
|
223
|
+
class ChildEnv:
|
|
224
|
+
"""One child env a composed card nests under ``children_environments``: its ``name`` and ``url`` as its card gives
|
|
225
|
+
them, and that ``card``, which says what it offers. A composer roots each child's ``url`` (and its endpoints) at
|
|
226
|
+
the route it serves the child at, so two children sharing a name have distinct urls; ``url`` is None for a child
|
|
227
|
+
whose card names none."""
|
|
228
|
+
|
|
229
|
+
name: str | None
|
|
230
|
+
url: str | None
|
|
231
|
+
card: dict = field(hash=False, repr=False)
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def extensions(self) -> list[dict]:
|
|
235
|
+
"""The extensions the child's card advertises (``capabilities.extensions``)."""
|
|
236
|
+
return list((self.card.get("capabilities") or {}).get("extensions") or [])
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def operations(self) -> list[str] | None:
|
|
240
|
+
"""The data-plane methods the child's card declares (``operations``), or None when it declares none."""
|
|
241
|
+
return operations(self.card)
|
|
242
|
+
|
|
243
|
+
def find_extension(self, uri: str) -> dict | None:
|
|
244
|
+
"""The extension the child's card advertises under ``uri``, or None (``find_extension``)."""
|
|
245
|
+
return find_extension(self.card, uri)
|
|
246
|
+
|
|
247
|
+
def serves(self, method: str) -> bool | None:
|
|
248
|
+
"""Whether the child's card serves the data-plane ``method``, None when it declares no operations (``serves``)."""
|
|
249
|
+
return serves(self.card, method)
|
|
250
|
+
|
|
251
|
+
def base_url(self, address: str) -> str:
|
|
252
|
+
"""Where the child's data plane is, its ``url`` without the RPC path, resolved against ``address``, the composed
|
|
253
|
+
card's: a path joins onto it, a full URL stands alone, and a child naming no url is at ``address`` itself."""
|
|
254
|
+
url = (self.url or RPC_PATH).removesuffix(RPC_PATH)
|
|
255
|
+
return url if "://" in url else f"{address.rstrip('/')}{url}"
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def children(card: dict) -> list[ChildEnv]:
|
|
259
|
+
"""Each child env a composed card nests under ``children_environments``, in its order; none for a leaf card, or
|
|
260
|
+
one whose ``children_environments`` is missing or null. An entry that isn't an object is skipped."""
|
|
261
|
+
return [ChildEnv(name=child.get("name"), url=child.get("url"), card=child)
|
|
262
|
+
for child in card.get("children_environments") or [] if isinstance(child, dict)]
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def child_at(card: dict, url: str) -> ChildEnv | None:
|
|
266
|
+
"""The child env of a composed card at ``url``: the one whose ``url`` is ``url``, or whose data plane is there
|
|
267
|
+
(``url`` without the RPC path, ``/svc/mcp-slack`` for ``/svc/mcp-slack/agentenv`` say), a trailing slash aside;
|
|
268
|
+
None when no child is. A composer serves each child at its own route, so this is the lookup two children sharing a
|
|
269
|
+
name can't confuse."""
|
|
270
|
+
wanted = url.rstrip("/")
|
|
271
|
+
for child in children(card):
|
|
272
|
+
if child.url is not None and wanted in (child.url.rstrip("/"), child.url.removesuffix(RPC_PATH).rstrip("/")):
|
|
273
|
+
return child
|
|
274
|
+
return None
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class AmbiguousChildError(LookupError):
|
|
278
|
+
"""More than one child env of a composed card has the name asked for, so the name can't say which is meant."""
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def child_named(card: dict, name: str) -> ChildEnv | None:
|
|
282
|
+
"""The one child env of a composed card named ``name``, or None when none is; raises ``AmbiguousChildError`` when
|
|
283
|
+
several are, as a website and an MCP server sharing a name are, which ``child_at`` tells apart by their urls."""
|
|
284
|
+
found = [child for child in children(card) if child.name == name]
|
|
285
|
+
if len(found) > 1:
|
|
286
|
+
raise AmbiguousChildError(f"{len(found)} child envs are named {name!r} ({', '.join(str(c.url) for c in found)}); "
|
|
287
|
+
"look one up by its url")
|
|
288
|
+
return found[0] if found else None
|
|
289
|
+
|
|
290
|
+
|
|
213
291
|
def find_child(card: dict, name: str) -> dict | None:
|
|
214
292
|
"""Return the nested child EnvironmentCard named `name`, or None.
|
|
215
293
|
|
|
@@ -218,6 +296,8 @@ def find_child(card: dict, name: str) -> dict | None:
|
|
|
218
296
|
rewritten to be reachable from the composed card's base_url. Callers resolve a service by
|
|
219
297
|
name here, then use the child card with `find_extension`/`invoke_extension`. Tolerates a
|
|
220
298
|
missing/null `children_environments` so single/leaf cards return None rather than raising.
|
|
299
|
+
Two children sharing `name` return the first; `child_named` refuses that, and `child_at`
|
|
300
|
+
finds a child by its url, which tells them apart.
|
|
221
301
|
"""
|
|
222
302
|
for child in card.get("children_environments") or []:
|
|
223
303
|
if child.get("name") == name:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
"""The base class's
|
|
2
|
-
session, so both act directly: a command inherits the session's environment, a file
|
|
1
|
+
"""The base class's defaults for the OS operations on Linux and macOS, which use POSIX alone. The driver runs as the
|
|
2
|
+
session user inside the desktop session, so both act directly: a command inherits the session's environment, a file
|
|
3
|
+
lands as that user."""
|
|
3
4
|
from __future__ import annotations
|
|
4
5
|
|
|
5
6
|
import asyncio
|
|
@@ -251,6 +251,15 @@ class AppDeclaration(BaseModel):
|
|
|
251
251
|
ready_checks: list[AppReadyCheck]
|
|
252
252
|
browser: Optional[bool] = None
|
|
253
253
|
|
|
254
|
+
@model_validator(mode="after")
|
|
255
|
+
def _no_window_wait_on_macos(self) -> AppDeclaration:
|
|
256
|
+
"""A window's readiness is read from X11's windows, which macOS hasn't."""
|
|
257
|
+
launch = self.os["macos"].launch if "macos" in self.os else None
|
|
258
|
+
if launch is not None and launch.ready is not None and launch.ready.window:
|
|
259
|
+
raise ValueError("on macos an app's launch can't wait on its window, which is read from X11; wait on a "
|
|
260
|
+
"port, a command or a @ready_check")
|
|
261
|
+
return self
|
|
262
|
+
|
|
254
263
|
@model_validator(mode="before")
|
|
255
264
|
@classmethod
|
|
256
265
|
def _stored_before_each_os(cls, data: Any) -> Any:
|
|
@@ -9,6 +9,7 @@ import json
|
|
|
9
9
|
import os
|
|
10
10
|
import shlex
|
|
11
11
|
import signal
|
|
12
|
+
import sys
|
|
12
13
|
import time
|
|
13
14
|
from collections.abc import Awaitable, Callable
|
|
14
15
|
from datetime import datetime, timezone
|
|
@@ -22,8 +23,9 @@ from . import session
|
|
|
22
23
|
if TYPE_CHECKING:
|
|
23
24
|
from .app import Launch
|
|
24
25
|
|
|
25
|
-
#: Where an app's server marks that it launched the app since the computer booted, at boot or on demand
|
|
26
|
-
|
|
26
|
+
#: Where an app's server marks that it launched the app since the computer booted, at boot or on demand: a folder a
|
|
27
|
+
#: boot clears, under /tmp on macOS, which has no /run and lets the session user write nothing at its root.
|
|
28
|
+
LAUNCHED = Path("/tmp/agentenv/apps" if sys.platform == "darwin" else "/run/agentenv/apps")
|
|
27
29
|
#: How long one readiness check may take; an app's checks run at once.
|
|
28
30
|
CHECK_TIMEOUT_S = 10.0
|
|
29
31
|
#: Where an app's server keeps the pid of the app it launched last, in the app's folder, so a server that restarts can
|
|
@@ -48,6 +48,8 @@ MINIMUM_TOOLS = ("computer_screenshot", "computer_click", "computer_drag", "comp
|
|
|
48
48
|
DATA_PLANE = (INTAKE_EXTENSION_URI, DATA_OBJECTS_EXTENSION_URI)
|
|
49
49
|
#: Where the fixture is staged and keeps its state, relative to the session user's home.
|
|
50
50
|
STAGE = ".agentenv-probe"
|
|
51
|
+
#: How the probe reads a staged file's owner: through the python3 the fixture runs on, the same on every OS.
|
|
52
|
+
OWNER = ("python3", "-c", "import os, sys; print(os.stat(sys.argv[1]).st_uid)")
|
|
51
53
|
#: Where a page driver's computer_upload takes files from, relative to the session user's home.
|
|
52
54
|
UPLOADS = "Uploads"
|
|
53
55
|
#: What the probe answers the page fixture's prompt with.
|
|
@@ -605,7 +607,7 @@ class Probe:
|
|
|
605
607
|
async def _security(self) -> None:
|
|
606
608
|
async def one_user() -> str | None:
|
|
607
609
|
uid = (await self.driver.run("id", "-u"))["stdout"].strip()
|
|
608
|
-
owner = (await self.driver.run(
|
|
610
|
+
owner = (await self.driver.run(*OWNER, f"{STAGE}/{self.fixture}"))["stdout"].strip()
|
|
609
611
|
if uid == "0":
|
|
610
612
|
return "commands run as root"
|
|
611
613
|
return None if owner == uid else f"data/add wrote as uid {owner}, commands run as uid {uid}"
|
|
@@ -205,13 +205,13 @@ class ComputerDriverEnvironment(Tools, Extensions, DataPlane, AgentEnvEnvironmen
|
|
|
205
205
|
if self._computer is None:
|
|
206
206
|
raise TypeError(f"{type(self).__qualname__}.__init__ must call super().__init__()")
|
|
207
207
|
|
|
208
|
-
# The
|
|
208
|
+
# The defaults for the OS operations on Linux and macOS, POSIX alike.
|
|
209
209
|
|
|
210
|
-
@default_for("run_in_session", os="linux")
|
|
210
|
+
@default_for("run_in_session", os=["linux", "macos"])
|
|
211
211
|
async def _computer_linux_run_in_session(self, argv: list, cwd: str | None, env: dict | None, timeout_s: float):
|
|
212
212
|
return await _linux.run_in_session(argv, cwd, env, timeout_s, output_bytes=self.settings.exec_output_bytes)
|
|
213
213
|
|
|
214
|
-
@default_for("write_files", os="linux")
|
|
214
|
+
@default_for("write_files", os=["linux", "macos"])
|
|
215
215
|
async def _computer_linux_write_files(self, files: dict) -> None:
|
|
216
216
|
await asyncio.to_thread(_linux.write_files, files)
|
|
217
217
|
|
|
@@ -27,13 +27,15 @@ class Platform:
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def detect(environ: dict | None = None, os_release: Path = Path("/etc/os-release")) -> Platform:
|
|
30
|
-
"""The running platform: the OS, ``/etc/os-release``'s ID and VERSION_ID on Linux
|
|
31
|
-
display from ``XDG_SESSION_TYPE``, ``none`` for a computer with no display, else
|
|
32
|
-
``WAYLAND_DISPLAY`` (wayland)."""
|
|
30
|
+
"""The running platform: the OS, ``/etc/os-release``'s ID and VERSION_ID on Linux and the macOS version on macOS,
|
|
31
|
+
the machine, and the display: on Linux from ``XDG_SESSION_TYPE``, ``none`` for a computer with no display, else
|
|
32
|
+
``DISPLAY`` (x11) or ``WAYLAND_DISPLAY`` (wayland); on macOS Aqua."""
|
|
33
33
|
environ = os.environ if environ is None else environ
|
|
34
34
|
name = _OS.get(sys.platform, sys.platform)
|
|
35
35
|
release = _os_release(os_release) if name == "linux" else {}
|
|
36
|
-
|
|
36
|
+
if name == "macos":
|
|
37
|
+
release = {"VERSION_ID": _platform.mac_ver()[0] or None}
|
|
38
|
+
display = "aqua" if name == "macos" else None
|
|
37
39
|
if name == "linux":
|
|
38
40
|
session = (environ.get("XDG_SESSION_TYPE") or "").strip().lower()
|
|
39
41
|
if session in ("x11", "wayland", "none"):
|
{agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/probe_fake.py
RENAMED
|
@@ -39,7 +39,7 @@ from agentenv_protocol.computer import (
|
|
|
39
39
|
upload,
|
|
40
40
|
)
|
|
41
41
|
from agentenv_protocol.computer import _linux as linux
|
|
42
|
-
from agentenv_protocol.computer.conformance.checks import STAGE
|
|
42
|
+
from agentenv_protocol.computer.conformance.checks import OWNER, STAGE
|
|
43
43
|
from agentenv_protocol.computer.conformance.page_fixture import QUESTION, TITLE, TWO, TWO_TITLE
|
|
44
44
|
|
|
45
45
|
CLIPBOARD = "agentenv-probe clipboard"
|
|
@@ -56,6 +56,7 @@ DESK_URL = "http://127.0.0.1:4343/"
|
|
|
56
56
|
TITLES = {DESK_URL: TITLE, DESK_URL + TWO.lstrip("/"): TWO_TITLE}
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
|
|
59
60
|
class FakeDesk:
|
|
60
61
|
"""What the Fixture Desk would record of the input the fake screen gets."""
|
|
61
62
|
|
|
@@ -202,7 +203,7 @@ class FixtureDriver(FakeDriver):
|
|
|
202
203
|
if f"python3 {STAGE}/fixture.py" in text:
|
|
203
204
|
self.desk.start()
|
|
204
205
|
return ExecResult(0, b"4242\n")
|
|
205
|
-
if argv in (["id", "-u"], [
|
|
206
|
+
if argv in (["id", "-u"], [*OWNER, f"{STAGE}/fixture.py"]):
|
|
206
207
|
return ExecResult(0, b"1000\n")
|
|
207
208
|
if argv[:1] in (["kill"], ["tail"]) or f"rm -rf {STAGE}/state" in text:
|
|
208
209
|
return ExecResult(0)
|
|
@@ -320,10 +321,10 @@ class PageFixtureDriver(FixtureDriver):
|
|
|
320
321
|
return ExecResult(0, b"4343") if self.desk.started else ExecResult(1)
|
|
321
322
|
if argv[:1] == ["cat"] and argv[1].endswith("layout.json") and self.desk.started:
|
|
322
323
|
return ExecResult(0, json.dumps(PAGE_LAYOUT).encode())
|
|
323
|
-
if argv == [
|
|
324
|
+
if argv == [*OWNER, f"{STAGE}/page_fixture.py"]:
|
|
324
325
|
return ExecResult(0, b"1000\n")
|
|
325
|
-
if argv == [
|
|
326
|
-
return ExecResult(1, b"", b"
|
|
326
|
+
if argv == [*OWNER, f"{STAGE}/fixture.py"]:
|
|
327
|
+
return ExecResult(1, b"", b"FileNotFoundError: [Errno 2] No such file or directory")
|
|
327
328
|
return super()._fixture(argv)
|
|
328
329
|
|
|
329
330
|
|
{agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_client.py
RENAMED
|
@@ -190,6 +190,58 @@ def test_find_child_tolerates_missing_children():
|
|
|
190
190
|
assert protocol_v1.find_child({"children_environments": None}, "slack") is None
|
|
191
191
|
|
|
192
192
|
|
|
193
|
+
_SHARED_NAME = {"name": "gw", "children_environments": [
|
|
194
|
+
{"name": "mail", "url": "/svc/mcp-mail/agentenv", "capabilities": {"operations": ["data/get"], "extensions": [
|
|
195
|
+
{"uri": "urn:agentenv:clock/v1"}]}},
|
|
196
|
+
{"name": "mail", "url": "/svc/mail/agentenv", "capabilities": {"operations": []}},
|
|
197
|
+
{"name": "docs", "capabilities": {}},
|
|
198
|
+
"not a card",
|
|
199
|
+
]}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_children_are_each_nested_card_by_its_name_url_and_card():
|
|
203
|
+
found = protocol_v1.children(_SHARED_NAME)
|
|
204
|
+
|
|
205
|
+
assert [(c.name, c.url) for c in found] == [("mail", "/svc/mcp-mail/agentenv"), ("mail", "/svc/mail/agentenv"), ("docs", None)]
|
|
206
|
+
assert found[0].card is _SHARED_NAME["children_environments"][0]
|
|
207
|
+
assert [c.extensions for c in found] == [[{"uri": "urn:agentenv:clock/v1"}], [], []]
|
|
208
|
+
assert [c.operations for c in found] == [["data/get"], [], None]
|
|
209
|
+
assert [c.serves("data/get") for c in found] == [True, False, None]
|
|
210
|
+
assert found[0].find_extension("urn:agentenv:clock/v1") is not None and found[1].find_extension("urn:agentenv:clock/v1") is None
|
|
211
|
+
assert protocol_v1.children({"name": "leaf"}) == [] and protocol_v1.children({"children_environments": None}) == []
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def test_a_childs_base_url_is_its_url_without_the_rpc_path_against_the_composed_cards_address():
|
|
215
|
+
mcp, website, docs = protocol_v1.children(_SHARED_NAME)
|
|
216
|
+
|
|
217
|
+
assert mcp.base_url("https://env.example/") == "https://env.example/svc/mcp-mail"
|
|
218
|
+
assert website.base_url("https://env.example") == "https://env.example/svc/mail"
|
|
219
|
+
assert docs.base_url("https://env.example") == "https://env.example"
|
|
220
|
+
assert protocol_v1.ChildEnv("x", "https://x.example/agentenv", {}).base_url("https://env.example") == "https://x.example"
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_child_at_finds_a_child_by_its_url_or_data_plane_route_where_two_share_a_name():
|
|
224
|
+
assert protocol_v1.child_at(_SHARED_NAME, "/svc/mail").url == "/svc/mail/agentenv"
|
|
225
|
+
assert protocol_v1.child_at(_SHARED_NAME, "/svc/mcp-mail/").url == "/svc/mcp-mail/agentenv"
|
|
226
|
+
assert protocol_v1.child_at(_SHARED_NAME, "/svc/mail/agentenv").url == "/svc/mail/agentenv"
|
|
227
|
+
assert protocol_v1.child_at(_SHARED_NAME, "/svc/docs") is None
|
|
228
|
+
assert protocol_v1.child_at({"name": "leaf"}, "/svc/mail") is None
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def test_child_named_refuses_a_name_two_children_share():
|
|
232
|
+
assert protocol_v1.child_named(_SHARED_NAME, "docs").name == "docs"
|
|
233
|
+
assert protocol_v1.child_named(_SHARED_NAME, "gmail") is None
|
|
234
|
+
with pytest.raises(protocol_v1.AmbiguousChildError, match="2 child envs are named 'mail'"):
|
|
235
|
+
protocol_v1.child_named(_SHARED_NAME, "mail")
|
|
236
|
+
assert protocol_v1.find_child(_SHARED_NAME, "mail")["url"] == "/svc/mcp-mail/agentenv"
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def test_serves_is_what_a_card_declares_of_a_method_and_none_when_it_declares_no_operations():
|
|
240
|
+
assert protocol_v1.serves({"capabilities": {"operations": ["data/get"]}}, "data/get") is True
|
|
241
|
+
assert protocol_v1.serves({"capabilities": {"operations": ["data/add"]}}, "data/get") is False
|
|
242
|
+
assert protocol_v1.serves({"capabilities": {}}, "data/get") is None
|
|
243
|
+
|
|
244
|
+
|
|
193
245
|
def test_find_extension_ignores_children():
|
|
194
246
|
# set-errors/v1 is advertised only on the children, never at the top level
|
|
195
247
|
assert protocol_v1.find_extension(_CARD_WITH_CHILDREN, "urn:agentenv:set-errors/v1") is None
|
|
@@ -6,6 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import asyncio
|
|
8
8
|
import contextlib
|
|
9
|
+
import importlib.util
|
|
9
10
|
import os
|
|
10
11
|
import shlex
|
|
11
12
|
import signal
|
|
@@ -109,6 +110,16 @@ async def test_an_apps_server_is_ready_once_it_launched_the_app_and_each_check_p
|
|
|
109
110
|
assert after["ready"] is True and after["problems"] == []
|
|
110
111
|
|
|
111
112
|
|
|
113
|
+
|
|
114
|
+
@pytest.mark.parametrize("platform, folder", [("linux", "/run/agentenv/apps"), ("darwin", "/tmp/agentenv/apps")])
|
|
115
|
+
def test_a_launch_is_marked_in_a_folder_a_boot_clears_and_the_session_user_writes(monkeypatch, platform, folder):
|
|
116
|
+
monkeypatch.setattr(sys, "platform", platform)
|
|
117
|
+
spec = importlib.util.spec_from_file_location(f"agentenv_protocol.computer._app_runtime_on_{platform}",
|
|
118
|
+
app_runtime.__file__)
|
|
119
|
+
loaded = importlib.util.module_from_spec(spec)
|
|
120
|
+
spec.loader.exec_module(loaded)
|
|
121
|
+
assert loaded.LAUNCHED == Path(folder)
|
|
122
|
+
|
|
112
123
|
@pytest.mark.asyncio
|
|
113
124
|
async def test_checks_that_fail_hang_or_raise_say_so_at_once_without_holding_up_the_server(desktop, monkeypatch):
|
|
114
125
|
monkeypatch.setattr(app_runtime, "CHECK_TIMEOUT_S", 1.0)
|
|
@@ -167,7 +167,7 @@ async def test_each_app_a_driver_fronts_that_exports_is_asked_for_its_export_onc
|
|
|
167
167
|
|
|
168
168
|
@pytest.mark.asyncio
|
|
169
169
|
async def test_a_driver_without_exec_or_data_add_passes_basic_and_leaves_full_unverified():
|
|
170
|
-
report = await _probe(MinimalDriver(FakeScreen(640, 400), platform=Platform(os="
|
|
170
|
+
report = await _probe(MinimalDriver(FakeScreen(640, 400), platform=Platform(os="windows")))
|
|
171
171
|
assert report.passed, _failed(report)
|
|
172
172
|
unverified = [c for c in report.checks if c.status == "unverified"]
|
|
173
173
|
assert {c.check for c in unverified} == {"P3", "P4", "P5", "P6", "P7", "P9"}
|
|
@@ -264,6 +264,9 @@ def test_an_app_declaring_its_skill_twice_is_refused(folder):
|
|
|
264
264
|
"Changed.fonts runs on 'windows', which Changed declares no install for; it declares freebsd, linux"),
|
|
265
265
|
('@ready_check(os="macos")\n def window_shown(self): return True',
|
|
266
266
|
"Changed.window_shown runs on 'macos', which Changed declares no install for"),
|
|
267
|
+
('@install("macos", FreeBSD(), launch=Launch(command=["mail"], window={"class": "Mail"}, '
|
|
268
|
+
'ready=Ready(window=True)))',
|
|
269
|
+
"on macos an app's launch can't wait on its window, which is read from X11"),
|
|
267
270
|
('class BuildSettings(BaseModel): key: SecretStr = SecretStr("")',
|
|
268
271
|
"Changed.BuildSettings holds a secret at properties.key, which its computer's image would keep; declare it in "
|
|
269
272
|
"Settings"),
|
|
@@ -273,9 +276,9 @@ def test_an_app_declaring_its_skill_twice_is_refused(folder):
|
|
|
273
276
|
"Changed.BuildSettings holds a secret at $defs.Settings.properties.password, which"),
|
|
274
277
|
], ids=["no-wait", "no-window", "window-key", "download-sha", "script-escape", "os-skill-twice", "os-build-secret",
|
|
275
278
|
"step-os",
|
|
276
|
-
"check-os", "build-secret", "build-secrets-listed", "build-secret-nested"])
|
|
279
|
+
"check-os", "macos-window-wait", "build-secret", "build-secrets-listed", "build-secret-nested"])
|
|
277
280
|
def test_an_app_whose_declaration_cant_be_checked_is_refused_saying_why(folder, capsys, change, problem):
|
|
278
|
-
decorator, body = (change, "pass") if change.startswith("@linux") else ("", change)
|
|
281
|
+
decorator, body = (change, "pass") if change.startswith(("@linux", "@install(")) else ("", change)
|
|
279
282
|
(folder / "app.py").write_text(APP.format(supports=LINUX, sha='a' * 64) + f"""
|
|
280
283
|
|
|
281
284
|
@computer_app(supports={LINUX})
|
|
@@ -45,6 +45,8 @@ from agentenv_protocol.computer.settings import load_settings # noqa: E402
|
|
|
45
45
|
from agentenv_protocol.computer.tools import _modifiers # noqa: E402
|
|
46
46
|
|
|
47
47
|
MACOS = Platform(os="macos", arch="arm64")
|
|
48
|
+
#: A platform the base class has no OS defaults for.
|
|
49
|
+
WINDOWS = Platform(os="windows", arch="amd64")
|
|
48
50
|
|
|
49
51
|
|
|
50
52
|
def _required(namespace: dict) -> dict:
|
|
@@ -130,7 +132,8 @@ def test_set_display_without_modes_is_refused():
|
|
|
130
132
|
def test_a_driver_method_replaces_the_linux_default():
|
|
131
133
|
Driver = _define({"run": run_in_session(lambda self, *a: None)})
|
|
132
134
|
assert declared(Driver)["run_in_session"] == [("run", {})]
|
|
133
|
-
assert declared(MinimalDriver)["run_in_session"] == [("_computer_linux_run_in_session",
|
|
135
|
+
assert declared(MinimalDriver)["run_in_session"] == [("_computer_linux_run_in_session",
|
|
136
|
+
{"os": frozenset({"linux", "macos"})})]
|
|
134
137
|
|
|
135
138
|
|
|
136
139
|
def test_the_platform_is_detected(tmp_path, monkeypatch):
|
|
@@ -143,7 +146,8 @@ def test_the_platform_is_detected(tmp_path, monkeypatch):
|
|
|
143
146
|
assert detect({"WAYLAND_DISPLAY": "wayland-0"}, release).display == "wayland"
|
|
144
147
|
assert detect({}, tmp_path / "missing").as_dict() == {"os": "linux", "arch": "arm64"}
|
|
145
148
|
monkeypatch.setattr("agentenv_protocol.computer.platform.sys.platform", "darwin")
|
|
146
|
-
|
|
149
|
+
monkeypatch.setattr("agentenv_protocol.computer.platform._platform.mac_ver", lambda: ("26.5.2", ("", "", ""), ""))
|
|
150
|
+
assert detect({"DISPLAY": ":0"}, release) == Platform("macos", None, "26.5.2", "arm64", "aqua")
|
|
147
151
|
|
|
148
152
|
|
|
149
153
|
def test_settings_come_from_one_json_value():
|
|
@@ -222,7 +226,7 @@ async def test_a_driver_whose_software_ships_a_skill_leads_with_it_and_the_compu
|
|
|
222
226
|
|
|
223
227
|
@pytest.mark.asyncio
|
|
224
228
|
async def test_a_minimal_driver_declares_only_what_it_has():
|
|
225
|
-
async with http_client(MinimalDriver(platform=
|
|
229
|
+
async with http_client(MinimalDriver(platform=WINDOWS)) as http:
|
|
226
230
|
card = (await http.get("/.well-known/agent-env.json")).json()
|
|
227
231
|
display_set = await http.post("/agentenv/ext/computer-display/set", json={"width": 1, "height": 1})
|
|
228
232
|
exec_run = await http.post("/agentenv/ext/computer-exec/run", json={"argv": ["true"], "request_id": "r"})
|
|
@@ -449,14 +453,14 @@ def test_a_driver_class_declares_what_it_serves_on_each_platform(monkeypatch):
|
|
|
449
453
|
raise AssertionError("declaration builds the card without the subclass's __init__")
|
|
450
454
|
|
|
451
455
|
linux = Declared.declaration(LINUX)
|
|
452
|
-
elsewhere = MinimalDriver.declaration(Platform(os="
|
|
456
|
+
elsewhere = MinimalDriver.declaration(Platform(os="windows"))
|
|
453
457
|
uris = lambda card: {e.uri for e in card.capabilities.extensions} # noqa: E731
|
|
454
458
|
assert linux.name == "declared" and len(linux.capabilities.tools) == 14
|
|
455
459
|
assert {"urn:agentenv:computer-pointer/v1", "urn:agentenv:computer-exec/v1"} <= uris(linux)
|
|
456
460
|
assert elsewhere.name == "MinimalDriver" and len(elsewhere.capabilities.tools) == 7
|
|
457
461
|
assert "urn:agentenv:computer-exec/v1" not in uris(elsewhere)
|
|
458
462
|
computer = next(e for e in elsewhere.capabilities.extensions if e.uri == "urn:agentenv:computer/v1")
|
|
459
|
-
assert computer.params["platform"] == {"os": "
|
|
463
|
+
assert computer.params["platform"] == {"os": "windows"}
|
|
460
464
|
|
|
461
465
|
|
|
462
466
|
def test_a_supports_entry_names_each_platform_it_covers():
|
|
@@ -94,12 +94,16 @@ def _grant() -> dict:
|
|
|
94
94
|
"file_field": "file"}}
|
|
95
95
|
|
|
96
96
|
|
|
97
|
-
async def _record(driver, seconds: float, start: dict | None = None, end: dict | None = None
|
|
98
|
-
|
|
97
|
+
async def _record(driver, seconds: float, start: dict | None = None, end: dict | None = None,
|
|
98
|
+
frames: int | None = None) -> tuple[dict, dict]:
|
|
99
|
+
"""A recording that runs ``seconds`` from its first frame, however long that took to come, and, given ``frames``,
|
|
100
|
+
until its encoder holds that many, and then ends."""
|
|
99
101
|
async with http_client(driver) as http:
|
|
100
102
|
started = (await http.post(START, json={"request_id": "r", "fps": 10, **(start or {})})).json()
|
|
101
103
|
await _first_frame(driver, started)
|
|
102
104
|
await asyncio.sleep(seconds)
|
|
105
|
+
if frames is not None:
|
|
106
|
+
await _encoded(driver, started, frames)
|
|
103
107
|
manifest = (await http.post(END, json={"recording_id": started["recording_id"], **(end or {})})).json()
|
|
104
108
|
return started, manifest
|
|
105
109
|
|
|
@@ -237,7 +241,7 @@ async def test_a_stream_that_keeps_failing_ends_the_recording_with_its_error(hom
|
|
|
237
241
|
|
|
238
242
|
@pytest.mark.asyncio
|
|
239
243
|
async def test_a_stream_of_frames_keeps_each_frames_capture_time(home):
|
|
240
|
-
_, manifest = await _record(Streamed(), 0.8, start={"fps": 20, "max_edge": 640})
|
|
244
|
+
_, manifest = await _record(Streamed(), 0.8, start={"fps": 20, "max_edge": 640}, frames=10)
|
|
241
245
|
(segment,) = manifest["segments"]
|
|
242
246
|
assert manifest["source"] == "stream" and (segment["width"], segment["height"]) == (640, 400)
|
|
243
247
|
frames = _decoded(home / ".agentenv-recordings" / segment["path"])
|
|
@@ -511,3 +515,12 @@ async def test_frames_in_thread_keeps_the_latest_frames_for_a_slow_reader_and_cl
|
|
|
511
515
|
await endless.aclose()
|
|
512
516
|
assert first == 0 and rest == [26, 27, 28, 29]
|
|
513
517
|
assert await asyncio.to_thread(stopped.wait, 5)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
async def _encoded(driver, started: dict, frames: int) -> None:
|
|
521
|
+
"""Until the recording's encoder holds ``frames`` frames: a busy machine captures fewer a second."""
|
|
522
|
+
recording = driver._computer.recordings.running[started["recording_id"]]
|
|
523
|
+
for _ in range(3000):
|
|
524
|
+
if recording.encoder and recording.encoder.frames >= frames:
|
|
525
|
+
return
|
|
526
|
+
await asyncio.sleep(0.01)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/THIRD_PARTY_NOTICES.md
RENAMED
|
File without changes
|
{agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/examples/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/examples/basic_agent.py
RENAMED
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
{agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/computer_fake.py
RENAMED
|
File without changes
|
{agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_a2a_agent.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_a2a_proxy.py
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_errors.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_intake.py
RENAMED
|
File without changes
|
{agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_manifest.py
RENAMED
|
File without changes
|
{agentenv_framework_protocol-0.1.453 → agentenv_framework_protocol-0.1.456}/tests/test_preflight.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|