openhands-agent-server 1.43.1__tar.gz → 1.44.1__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.
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/PKG-INFO +1 -1
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/agent_profiles_router.py +7 -3
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/api.py +2 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/canvas_extensions/__init__.py +4 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/canvas_extensions/installed.py +41 -0
- openhands_agent_server-1.44.1/openhands/agent_server/canvas_extensions_router.py +291 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/config.py +15 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/conversation_service.py +138 -18
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/docker/Dockerfile +103 -53
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/docker/build.py +36 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/event_service.py +7 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/persistence/models.py +48 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/persistence/store.py +2 -2
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/pub_sub.py +7 -1
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/sockets.py +3 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands_agent_server.egg-info/PKG-INFO +1 -1
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands_agent_server.egg-info/SOURCES.txt +2 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/pyproject.toml +1 -1
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/__init__.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/__main__.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/_secret_redaction.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/_secrets_exposure.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/auth_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/bash_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/bash_service.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/canvas_extensions/manifest.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/conversation_lease.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/conversation_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/credential_binding.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/dependencies.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/desktop_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/desktop_service.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/docker/wallpaper.svg +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/env_parser.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/event_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/file_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/git_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/hooks_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/hooks_service.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/init_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/llm_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/logging_config.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/mcp_oauth_store.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/mcp_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/middleware.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/models.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/openai/__init__.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/openai/models.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/openai/router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/openai/service.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/openapi.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/persistence/__init__.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/plugins_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/plugins_service.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/profiles_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/provider_connections_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/py.typed +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/server_details_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/settings_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/skills_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/skills_service.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/sub_agents_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/__init__.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/factory.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/http_exporter.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/models.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/policy.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/posthog_exporter.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/sanitizer.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/service.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/sink.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry/subscriber.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/telemetry_types.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/tool_preload_service.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/tool_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/utils.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/vscode_extensions/openhands-settings/extension.js +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/vscode_extensions/openhands-settings/package.json +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/vscode_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/vscode_service.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/workspace_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/workspaces_router.py +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands_agent_server.egg-info/dependency_links.txt +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands_agent_server.egg-info/entry_points.txt +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands_agent_server.egg-info/requires.txt +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands_agent_server.egg-info/top_level.txt +0 -0
- {openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-agent-server
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.44.1
|
|
4
4
|
Summary: OpenHands Agent Server - REST/WebSocket interface for OpenHands AI Agent
|
|
5
5
|
Project-URL: Source, https://github.com/OpenHands/software-agent-sdk
|
|
6
6
|
Project-URL: Homepage, https://github.com/OpenHands/software-agent-sdk
|
|
@@ -514,13 +514,17 @@ async def materialize_agent_profile(
|
|
|
514
514
|
mcp_config = settings.agent_settings.mcp_config
|
|
515
515
|
|
|
516
516
|
# Discover skills off the event loop so the dry-run can report which skills
|
|
517
|
-
# (catalog minus ``disabled_skills``) resolve.
|
|
518
|
-
#
|
|
517
|
+
# (catalog minus ``disabled_skills``) resolve. Mirrors the launch rule in
|
|
518
|
+
# ``conversation_service._resolve_agent_from_profile`` so the preview matches
|
|
519
|
+
# a real launch: an ACP profile is only given a catalog where the CLI cannot
|
|
520
|
+
# read the user's own configuration (#4019). A discovery failure must not 500
|
|
519
521
|
# the preview: pass ``available_skills=None`` and surface the failure as its
|
|
520
522
|
# own diagnostic below.
|
|
521
523
|
discovery_error: str | None = None
|
|
522
524
|
available_skills = None
|
|
523
|
-
if profile.agent_kind == "openhands"
|
|
525
|
+
if profile.agent_kind == "openhands" or (
|
|
526
|
+
config.acp_skill_sourcing == "openhands_managed"
|
|
527
|
+
):
|
|
524
528
|
try:
|
|
525
529
|
available_skills = await asyncio.to_thread(discover_profile_skills)
|
|
526
530
|
except Exception as exc:
|
{openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/api.py
RENAMED
|
@@ -21,6 +21,7 @@ from openhands.agent_server.agent_profiles_router import agent_profiles_router
|
|
|
21
21
|
from openhands.agent_server.auth_router import auth_router
|
|
22
22
|
from openhands.agent_server.bash_router import bash_router
|
|
23
23
|
from openhands.agent_server.bash_service import get_default_bash_event_service
|
|
24
|
+
from openhands.agent_server.canvas_extensions_router import canvas_extensions_router
|
|
24
25
|
from openhands.agent_server.config import (
|
|
25
26
|
Config,
|
|
26
27
|
get_default_config,
|
|
@@ -441,6 +442,7 @@ def _add_api_routes(app: FastAPI) -> None:
|
|
|
441
442
|
api_router.include_router(skills_router)
|
|
442
443
|
api_router.include_router(sub_agents_router)
|
|
443
444
|
api_router.include_router(plugins_router)
|
|
445
|
+
api_router.include_router(canvas_extensions_router)
|
|
444
446
|
api_router.include_router(hooks_router)
|
|
445
447
|
api_router.include_router(llm_router)
|
|
446
448
|
api_router.include_router(provider_connections_router)
|
|
@@ -7,7 +7,9 @@ from openhands.agent_server.canvas_extensions.installed import (
|
|
|
7
7
|
check_canvas_extension_update,
|
|
8
8
|
disable_canvas_extension,
|
|
9
9
|
enable_canvas_extension,
|
|
10
|
+
get_canvas_extension_bundle_path,
|
|
10
11
|
get_installed_canvas_extension,
|
|
12
|
+
get_installed_canvas_extension_manifest,
|
|
11
13
|
get_installed_canvas_extensions_dir,
|
|
12
14
|
install_canvas_extension,
|
|
13
15
|
list_installed_canvas_extensions,
|
|
@@ -37,7 +39,9 @@ __all__ = [
|
|
|
37
39
|
"list_installed_canvas_extensions",
|
|
38
40
|
"load_installed_canvas_extensions",
|
|
39
41
|
"get_installed_canvas_extension",
|
|
42
|
+
"get_installed_canvas_extension_manifest",
|
|
40
43
|
"get_installed_canvas_extensions_dir",
|
|
44
|
+
"get_canvas_extension_bundle_path",
|
|
41
45
|
"CanvasExtensionUpdateCheck",
|
|
42
46
|
"check_canvas_extension_update",
|
|
43
47
|
"apply_canvas_extension_update",
|
|
@@ -206,6 +206,47 @@ def get_installed_canvas_extension(
|
|
|
206
206
|
return _manager(_resolve_installed_dir(installed_dir)).get(name)
|
|
207
207
|
|
|
208
208
|
|
|
209
|
+
def get_installed_canvas_extension_manifest(
|
|
210
|
+
name: str, installed_dir: Path | None = None
|
|
211
|
+
) -> CanvasExtensionManifest | None:
|
|
212
|
+
"""Load the validated manifest for an installed extension.
|
|
213
|
+
|
|
214
|
+
Re-reads and re-validates the stored manifest against the live install
|
|
215
|
+
path -- the copy validated at install time may have changed on disk
|
|
216
|
+
since.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
None if not installed, or if the install no longer validates.
|
|
220
|
+
"""
|
|
221
|
+
installed_dir = _resolve_installed_dir(installed_dir)
|
|
222
|
+
if get_installed_canvas_extension(name, installed_dir) is None:
|
|
223
|
+
return None
|
|
224
|
+
try:
|
|
225
|
+
return CanvasExtensionInstallationInterface.load_from_dir(installed_dir / name)
|
|
226
|
+
except (ValidationError, ValueError, OSError):
|
|
227
|
+
return None
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def get_canvas_extension_bundle_path(
|
|
231
|
+
name: str, installed_dir: Path | None = None
|
|
232
|
+
) -> Path | None:
|
|
233
|
+
"""Resolve the on-disk path to *name*'s entrypoint bundle file.
|
|
234
|
+
|
|
235
|
+
Re-validates the manifest and entrypoint containment against the live
|
|
236
|
+
install path on every call -- this backs a bundle serve, so the check
|
|
237
|
+
done at install time isn't enough on its own (a symlink could change
|
|
238
|
+
between requests).
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
None if not installed, or if the install no longer validates.
|
|
242
|
+
"""
|
|
243
|
+
installed_dir = _resolve_installed_dir(installed_dir)
|
|
244
|
+
manifest = get_installed_canvas_extension_manifest(name, installed_dir)
|
|
245
|
+
if manifest is None:
|
|
246
|
+
return None
|
|
247
|
+
return resolve_entrypoint(manifest, installed_dir / name)
|
|
248
|
+
|
|
249
|
+
|
|
209
250
|
class CanvasExtensionUpdateCheck(BaseModel):
|
|
210
251
|
"""Result of ``check_canvas_extension_update``.
|
|
211
252
|
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""Canvas Extensions router for OpenHands Agent Server.
|
|
2
|
+
|
|
3
|
+
HTTP API endpoints for canvas extension operations. Business logic is
|
|
4
|
+
delegated to ``canvas_extensions/installed.py``; this module mirrors
|
|
5
|
+
``plugins_router.py`` / ``skills_router.py`` and stays focused on HTTP
|
|
6
|
+
concerns: install / list / get / enable-disable / uninstall, plus serving
|
|
7
|
+
an installed extension's entrypoint bundle to the Canvas frontend.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Annotated, Final
|
|
11
|
+
|
|
12
|
+
from fastapi import APIRouter, HTTPException, Path
|
|
13
|
+
from fastapi.responses import FileResponse
|
|
14
|
+
from pydantic import BaseModel, Field, ValidationError
|
|
15
|
+
|
|
16
|
+
from openhands.agent_server.canvas_extensions.installed import (
|
|
17
|
+
InstalledCanvasExtensionInfo,
|
|
18
|
+
disable_canvas_extension,
|
|
19
|
+
enable_canvas_extension,
|
|
20
|
+
get_canvas_extension_bundle_path,
|
|
21
|
+
get_installed_canvas_extension,
|
|
22
|
+
get_installed_canvas_extension_manifest,
|
|
23
|
+
install_canvas_extension,
|
|
24
|
+
list_installed_canvas_extensions,
|
|
25
|
+
uninstall_canvas_extension,
|
|
26
|
+
)
|
|
27
|
+
from openhands.agent_server.canvas_extensions.manifest import CanvasExtensionManifest
|
|
28
|
+
from openhands.sdk.extensions.fetch import ExtensionFetchError
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
canvas_extensions_router = APIRouter(
|
|
32
|
+
prefix="/canvas-extensions", tags=["Canvas Extensions"]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Matches the SDK's validate_extension_name() rule.
|
|
36
|
+
CANVAS_EXTENSION_NAME_PATTERN: Final[str] = r"^[a-z0-9]+(?:-[a-z0-9]+)*$"
|
|
37
|
+
|
|
38
|
+
CanvasExtensionNamePath = Annotated[
|
|
39
|
+
str,
|
|
40
|
+
Path(
|
|
41
|
+
min_length=1,
|
|
42
|
+
max_length=255,
|
|
43
|
+
pattern=CANVAS_EXTENSION_NAME_PATTERN,
|
|
44
|
+
description="Canvas extension name (lowercase alphanumeric, hyphens)",
|
|
45
|
+
),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class InstallCanvasExtensionRequest(BaseModel):
|
|
50
|
+
"""Request body for installing a canvas extension."""
|
|
51
|
+
|
|
52
|
+
source: str = Field(
|
|
53
|
+
min_length=1,
|
|
54
|
+
description=(
|
|
55
|
+
"Canvas extension source - git URL, GitHub shorthand, or local "
|
|
56
|
+
"path. Examples: 'github:owner/repo', '/path/to/extension'"
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
ref: str | None = Field(
|
|
60
|
+
default=None, description="Optional branch, tag, or commit to install"
|
|
61
|
+
)
|
|
62
|
+
repo_path: str | None = Field(
|
|
63
|
+
default=None,
|
|
64
|
+
description="Subdirectory path within the repository (for monorepos)",
|
|
65
|
+
)
|
|
66
|
+
force: bool = Field(
|
|
67
|
+
default=False, description="If true, overwrite existing installation"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class InstalledCanvasExtensionResponse(BaseModel):
|
|
72
|
+
"""Response containing installed canvas extension information."""
|
|
73
|
+
|
|
74
|
+
name: str = Field(description="Canvas extension name")
|
|
75
|
+
version: str = Field(default="", description="Canvas extension version")
|
|
76
|
+
description: str = Field(default="", description="Canvas extension description")
|
|
77
|
+
enabled: bool = Field(
|
|
78
|
+
default=False, description="Whether the canvas extension is enabled"
|
|
79
|
+
)
|
|
80
|
+
source: str = Field(description="Original source (e.g., 'github:owner/repo')")
|
|
81
|
+
resolved_ref: str | None = Field(
|
|
82
|
+
default=None, description="Resolved git commit SHA"
|
|
83
|
+
)
|
|
84
|
+
repo_path: str | None = Field(
|
|
85
|
+
default=None, description="Subdirectory path within the repository"
|
|
86
|
+
)
|
|
87
|
+
installed_at: str = Field(description="ISO 8601 timestamp of installation")
|
|
88
|
+
install_path: str = Field(description="Path where the extension is installed")
|
|
89
|
+
manifest: CanvasExtensionManifest | None = Field(
|
|
90
|
+
default=None,
|
|
91
|
+
description=(
|
|
92
|
+
"The extension's validated manifest (display_name, "
|
|
93
|
+
"contributes.pages, ...). None if the installed manifest can "
|
|
94
|
+
"no longer be read."
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def from_info(
|
|
100
|
+
cls,
|
|
101
|
+
info: InstalledCanvasExtensionInfo,
|
|
102
|
+
manifest: CanvasExtensionManifest | None,
|
|
103
|
+
) -> "InstalledCanvasExtensionResponse":
|
|
104
|
+
return cls(
|
|
105
|
+
name=info.name,
|
|
106
|
+
version=info.version,
|
|
107
|
+
description=info.description,
|
|
108
|
+
enabled=info.enabled,
|
|
109
|
+
source=info.source,
|
|
110
|
+
resolved_ref=info.resolved_ref,
|
|
111
|
+
repo_path=info.repo_path,
|
|
112
|
+
installed_at=info.installed_at,
|
|
113
|
+
install_path=str(info.install_path),
|
|
114
|
+
manifest=manifest,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class InstalledCanvasExtensionsListResponse(BaseModel):
|
|
119
|
+
"""Response containing the list of installed canvas extensions."""
|
|
120
|
+
|
|
121
|
+
canvas_extensions: list[InstalledCanvasExtensionResponse]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class UpdateCanvasExtensionStateRequest(BaseModel):
|
|
125
|
+
"""Request body for updating canvas extension state (enable/disable)."""
|
|
126
|
+
|
|
127
|
+
enabled: bool
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class UpdateCanvasExtensionStateResponse(BaseModel):
|
|
131
|
+
"""Response from a canvas extension state update."""
|
|
132
|
+
|
|
133
|
+
name: str
|
|
134
|
+
enabled: bool
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class UninstallCanvasExtensionResponse(BaseModel):
|
|
138
|
+
"""Response from a canvas extension uninstall."""
|
|
139
|
+
|
|
140
|
+
message: str
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@canvas_extensions_router.post(
|
|
144
|
+
"/install",
|
|
145
|
+
response_model=InstalledCanvasExtensionResponse,
|
|
146
|
+
responses={
|
|
147
|
+
400: {"description": "Failed to fetch canvas extension source"},
|
|
148
|
+
409: {"description": "Canvas extension already installed (use force=true)"},
|
|
149
|
+
422: {"description": "Invalid canvas extension (bad manifest, etc.)"},
|
|
150
|
+
},
|
|
151
|
+
)
|
|
152
|
+
def install_canvas_extension_endpoint(
|
|
153
|
+
request: InstallCanvasExtensionRequest,
|
|
154
|
+
) -> InstalledCanvasExtensionResponse:
|
|
155
|
+
"""Install a canvas extension from a git URL, GitHub shorthand, or local
|
|
156
|
+
path.
|
|
157
|
+
|
|
158
|
+
A fresh install always lands disabled, regardless of the request body --
|
|
159
|
+
there is no ``enabled`` field to smuggle a different state through.
|
|
160
|
+
"""
|
|
161
|
+
try:
|
|
162
|
+
info = install_canvas_extension(
|
|
163
|
+
source=request.source,
|
|
164
|
+
ref=request.ref,
|
|
165
|
+
repo_path=request.repo_path,
|
|
166
|
+
force=request.force,
|
|
167
|
+
)
|
|
168
|
+
return InstalledCanvasExtensionResponse.from_info(
|
|
169
|
+
info, get_installed_canvas_extension_manifest(info.name)
|
|
170
|
+
)
|
|
171
|
+
except FileExistsError:
|
|
172
|
+
raise HTTPException(
|
|
173
|
+
status_code=409,
|
|
174
|
+
detail="Canvas extension already installed. Use force=true to overwrite.",
|
|
175
|
+
)
|
|
176
|
+
except ExtensionFetchError:
|
|
177
|
+
raise HTTPException(
|
|
178
|
+
status_code=400,
|
|
179
|
+
detail=(
|
|
180
|
+
"Failed to fetch canvas extension source. "
|
|
181
|
+
"Check that the source is valid."
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
except (ValidationError, ValueError, OSError):
|
|
185
|
+
raise HTTPException(
|
|
186
|
+
status_code=422,
|
|
187
|
+
detail="Invalid canvas extension. Ensure the manifest is well-formed.",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@canvas_extensions_router.get(
|
|
192
|
+
"/installed", response_model=InstalledCanvasExtensionsListResponse
|
|
193
|
+
)
|
|
194
|
+
def list_installed_canvas_extensions_endpoint() -> (
|
|
195
|
+
InstalledCanvasExtensionsListResponse
|
|
196
|
+
):
|
|
197
|
+
"""List all installed canvas extensions (enabled and disabled)."""
|
|
198
|
+
infos = list_installed_canvas_extensions()
|
|
199
|
+
return InstalledCanvasExtensionsListResponse(
|
|
200
|
+
canvas_extensions=[
|
|
201
|
+
InstalledCanvasExtensionResponse.from_info(
|
|
202
|
+
i, get_installed_canvas_extension_manifest(i.name)
|
|
203
|
+
)
|
|
204
|
+
for i in infos
|
|
205
|
+
]
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@canvas_extensions_router.get(
|
|
210
|
+
"/installed/{extension_name}",
|
|
211
|
+
response_model=InstalledCanvasExtensionResponse,
|
|
212
|
+
responses={404: {"description": "Canvas extension not installed"}},
|
|
213
|
+
)
|
|
214
|
+
def get_installed_canvas_extension_endpoint(
|
|
215
|
+
extension_name: CanvasExtensionNamePath,
|
|
216
|
+
) -> InstalledCanvasExtensionResponse:
|
|
217
|
+
"""Get information about a specific installed canvas extension."""
|
|
218
|
+
info = get_installed_canvas_extension(name=extension_name)
|
|
219
|
+
if info is None:
|
|
220
|
+
raise HTTPException(
|
|
221
|
+
status_code=404,
|
|
222
|
+
detail=f"Canvas extension '{extension_name}' is not installed",
|
|
223
|
+
)
|
|
224
|
+
return InstalledCanvasExtensionResponse.from_info(
|
|
225
|
+
info, get_installed_canvas_extension_manifest(extension_name)
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@canvas_extensions_router.patch(
|
|
230
|
+
"/installed/{extension_name}",
|
|
231
|
+
response_model=UpdateCanvasExtensionStateResponse,
|
|
232
|
+
responses={404: {"description": "Canvas extension not installed"}},
|
|
233
|
+
)
|
|
234
|
+
def set_canvas_extension_enabled_endpoint(
|
|
235
|
+
extension_name: CanvasExtensionNamePath, request: UpdateCanvasExtensionStateRequest
|
|
236
|
+
) -> UpdateCanvasExtensionStateResponse:
|
|
237
|
+
"""Enable or disable an installed canvas extension."""
|
|
238
|
+
fn = enable_canvas_extension if request.enabled else disable_canvas_extension
|
|
239
|
+
if not fn(name=extension_name):
|
|
240
|
+
raise HTTPException(
|
|
241
|
+
status_code=404,
|
|
242
|
+
detail=f"Canvas extension '{extension_name}' is not installed",
|
|
243
|
+
)
|
|
244
|
+
return UpdateCanvasExtensionStateResponse(
|
|
245
|
+
name=extension_name, enabled=request.enabled
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@canvas_extensions_router.delete(
|
|
250
|
+
"/installed/{extension_name}",
|
|
251
|
+
response_model=UninstallCanvasExtensionResponse,
|
|
252
|
+
responses={404: {"description": "Canvas extension not installed"}},
|
|
253
|
+
)
|
|
254
|
+
def uninstall_canvas_extension_endpoint(
|
|
255
|
+
extension_name: CanvasExtensionNamePath,
|
|
256
|
+
) -> UninstallCanvasExtensionResponse:
|
|
257
|
+
"""Uninstall a canvas extension by name."""
|
|
258
|
+
if not uninstall_canvas_extension(name=extension_name):
|
|
259
|
+
raise HTTPException(
|
|
260
|
+
status_code=404,
|
|
261
|
+
detail=f"Canvas extension '{extension_name}' is not installed",
|
|
262
|
+
)
|
|
263
|
+
return UninstallCanvasExtensionResponse(
|
|
264
|
+
message=f"Canvas extension '{extension_name}' uninstalled"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@canvas_extensions_router.get(
|
|
269
|
+
"/installed/{extension_name}/bundle",
|
|
270
|
+
responses={404: {"description": "Canvas extension or bundle not found"}},
|
|
271
|
+
)
|
|
272
|
+
def get_canvas_extension_bundle_endpoint(
|
|
273
|
+
extension_name: CanvasExtensionNamePath,
|
|
274
|
+
) -> FileResponse:
|
|
275
|
+
"""Serve an installed canvas extension's entrypoint bundle file.
|
|
276
|
+
|
|
277
|
+
Entrypoint containment is re-validated against the live install path on
|
|
278
|
+
every request, not just at install time -- see
|
|
279
|
+
``get_canvas_extension_bundle_path``. ``Cache-Control: no-cache`` forces
|
|
280
|
+
revalidation on each request, so a client never keeps serving a bundle
|
|
281
|
+
from before the extension's last refresh: the actual bytes are re-read
|
|
282
|
+
from disk, and Starlette's file-stat-based ``ETag``/``Last-Modified``
|
|
283
|
+
change the moment the on-disk revision does.
|
|
284
|
+
"""
|
|
285
|
+
bundle_path = get_canvas_extension_bundle_path(name=extension_name)
|
|
286
|
+
if bundle_path is None:
|
|
287
|
+
raise HTTPException(
|
|
288
|
+
status_code=404,
|
|
289
|
+
detail=f"Canvas extension '{extension_name}' bundle not found",
|
|
290
|
+
)
|
|
291
|
+
return FileResponse(bundle_path, headers={"Cache-Control": "no-cache"})
|
{openhands_agent_server-1.43.1 → openhands_agent_server-1.44.1}/openhands/agent_server/config.py
RENAMED
|
@@ -27,6 +27,7 @@ CONFIG_PATH_ENV = "OPENHANDS_AGENT_SERVER_CONFIG_PATH"
|
|
|
27
27
|
DEFAULT_CONFIG_PATH = Path("workspace/openhands_agent_server_config.json")
|
|
28
28
|
# 20 minutes, matching the idle timeout used by OpenHands Cloud.
|
|
29
29
|
DEFAULT_CONVERSATION_IDLE_TTL_SECONDS: Final[float] = 20 * 60.0
|
|
30
|
+
ACPSkillSourcing = Literal["native", "openhands_managed"]
|
|
30
31
|
_logger = logging.getLogger(__name__)
|
|
31
32
|
|
|
32
33
|
|
|
@@ -349,6 +350,20 @@ class Config(BaseModel):
|
|
|
349
350
|
"The URL where this agent server instance is available externally"
|
|
350
351
|
),
|
|
351
352
|
)
|
|
353
|
+
acp_skill_sourcing: ACPSkillSourcing = Field(
|
|
354
|
+
default="native",
|
|
355
|
+
description=(
|
|
356
|
+
"Who supplies an ACP agent's skills. 'native' (the default, for a "
|
|
357
|
+
"host-local agent-server): nobody but the ACP CLI — it reads the "
|
|
358
|
+
"user's own home configuration and the repository, so OpenHands "
|
|
359
|
+
"injects none of its managed skills. 'openhands_managed' (for "
|
|
360
|
+
"container runtimes, where that host configuration is absent): also "
|
|
361
|
+
"inject the user/org/public/marketplace skills the server "
|
|
362
|
+
"discovers. Project/repository skills are never injected either way "
|
|
363
|
+
"— the CLI reads AGENTS.md itself (#4019). Set explicitly per "
|
|
364
|
+
"deployment; the agent-server image sets 'openhands_managed'."
|
|
365
|
+
),
|
|
366
|
+
)
|
|
352
367
|
registered_marketplaces: list[MarketplaceRegistration] = Field(
|
|
353
368
|
default_factory=list,
|
|
354
369
|
description=(
|