openhands-agent-server 1.34.0__tar.gz → 1.35.0__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.34.0 → openhands_agent_server-1.35.0}/PKG-INFO +1 -1
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/docker/Dockerfile +1 -1
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/file_router.py +23 -5
- openhands_agent_server-1.35.0/openhands/agent_server/git_router.py +181 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/llm_router.py +3 -2
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands_agent_server.egg-info/PKG-INFO +1 -1
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/pyproject.toml +1 -1
- openhands_agent_server-1.34.0/openhands/agent_server/git_router.py +0 -88
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/__init__.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/__main__.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/_secrets_exposure.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/agent_profiles_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/api.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/auth_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/bash_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/bash_service.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/config.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/conversation_lease.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/conversation_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/conversation_service.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/dependencies.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/desktop_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/desktop_service.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/docker/build.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/docker/wallpaper.svg +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/env_parser.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/event_compat.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/event_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/event_service.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/hooks_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/hooks_service.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/init_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/logging_config.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/mcp_oauth_store.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/mcp_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/middleware.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/models.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/openai/__init__.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/openai/models.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/openai/router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/openai/service.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/openapi.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/persistence/__init__.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/persistence/models.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/persistence/store.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/plugins_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/plugins_service.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/profiles_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/pub_sub.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/py.typed +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/server_details_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/settings_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/skills_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/skills_service.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/sockets.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/sub_agents_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/tool_preload_service.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/tool_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/utils.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/vscode_extensions/openhands-settings/extension.js +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/vscode_extensions/openhands-settings/package.json +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/vscode_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/vscode_service.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/workspace_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/workspaces_router.py +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands_agent_server.egg-info/SOURCES.txt +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands_agent_server.egg-info/dependency_links.txt +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands_agent_server.egg-info/entry_points.txt +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands_agent_server.egg-info/requires.txt +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands_agent_server.egg-info/top_level.txt +0 -0
- {openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/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.35.0
|
|
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
|
|
@@ -181,7 +181,7 @@ RUN set -ux; \
|
|
|
181
181
|
PATH="$ACP_NODE_DIR/bin:$PATH"; \
|
|
182
182
|
if "$ACP_NODE_DIR/bin/npm" install -g \
|
|
183
183
|
@agentclientprotocol/claude-agent-acp@0.44.0 \
|
|
184
|
-
@
|
|
184
|
+
@agentclientprotocol/codex-acp@1.1.2 \
|
|
185
185
|
@google/gemini-cli@0.46.0; then \
|
|
186
186
|
# Create wrappers in /usr/local/bin that prepend ACP's Node 22 to PATH.
|
|
187
187
|
# This ensures the ACP binary's #!/usr/bin/env node shebang resolves
|
|
@@ -10,6 +10,7 @@ import tempfile
|
|
|
10
10
|
import zipfile
|
|
11
11
|
from pathlib import Path, PurePosixPath
|
|
12
12
|
from typing import IO, Annotated, Literal
|
|
13
|
+
from urllib.parse import quote
|
|
13
14
|
from uuid import UUID
|
|
14
15
|
|
|
15
16
|
from fastapi import (
|
|
@@ -30,6 +31,7 @@ from openhands.agent_server.server_details_router import update_last_execution_t
|
|
|
30
31
|
from openhands.sdk.git.exceptions import GitCommandError, GitRepositoryError
|
|
31
32
|
from openhands.sdk.git.utils import (
|
|
32
33
|
GIT_EMPTY_TREE_HASH,
|
|
34
|
+
get_git_repository_metadata,
|
|
33
35
|
get_valid_ref,
|
|
34
36
|
run_git_command,
|
|
35
37
|
validate_git_repository,
|
|
@@ -323,6 +325,11 @@ def _head_is_detached(root: Path) -> bool:
|
|
|
323
325
|
return branch.strip() == "HEAD"
|
|
324
326
|
|
|
325
327
|
|
|
328
|
+
def _header_safe(value: str) -> str:
|
|
329
|
+
"""Percent-encode a header value."""
|
|
330
|
+
return quote(value, safe="")
|
|
331
|
+
|
|
332
|
+
|
|
326
333
|
def _create_git_delta(
|
|
327
334
|
root: Path, base_ref: str | None, output_path: Path, excludes: list[str]
|
|
328
335
|
) -> str:
|
|
@@ -972,13 +979,24 @@ async def archive_directory(
|
|
|
972
979
|
detail=f"Failed to archive directory: {str(e)}",
|
|
973
980
|
)
|
|
974
981
|
|
|
975
|
-
|
|
982
|
+
# Repo identity (remote / branch / HEAD) applies to both formats so a
|
|
983
|
+
# tar.gz snapshot is self-describing too; base_commit/base_ref are
|
|
984
|
+
# git-delta-only (they describe the patch's replay base).
|
|
985
|
+
headers: dict[str, str] = {}
|
|
986
|
+
if (repo_root / ".git").exists():
|
|
987
|
+
repo_metadata = await asyncio.to_thread(get_git_repository_metadata, repo_root)
|
|
988
|
+
for key, header in (
|
|
989
|
+
("repo_remote", "X-Archive-Repo-Remote"),
|
|
990
|
+
("branch", "X-Archive-Branch"),
|
|
991
|
+
("head_commit", "X-Archive-Head-Commit"),
|
|
992
|
+
):
|
|
993
|
+
if value := repo_metadata.get(key):
|
|
994
|
+
headers[header] = _header_safe(value)
|
|
995
|
+
headers["X-Archive-Repo-Root"] = _header_safe(str(repo_root))
|
|
976
996
|
if base_commit:
|
|
977
997
|
# Make a git-delta self-describing so consumers can replay the patch.
|
|
978
|
-
headers =
|
|
979
|
-
|
|
980
|
-
"X-Archive-Base-Ref": base_ref or "auto",
|
|
981
|
-
}
|
|
998
|
+
headers["X-Archive-Base-Commit"] = base_commit
|
|
999
|
+
headers["X-Archive-Base-Ref"] = base_ref or "auto"
|
|
982
1000
|
|
|
983
1001
|
return FileResponse(
|
|
984
1002
|
path=output_path,
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Git router for OpenHands SDK."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import functools
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from fastapi import APIRouter, HTTPException, Path as PathParam, Query
|
|
9
|
+
|
|
10
|
+
from openhands.agent_server.server_details_router import update_last_execution_time
|
|
11
|
+
from openhands.sdk.git.exceptions import GitError, GitRepositoryError
|
|
12
|
+
from openhands.sdk.git.git_changes import get_git_changes
|
|
13
|
+
from openhands.sdk.git.git_commits import (
|
|
14
|
+
get_commit_changes,
|
|
15
|
+
get_commit_file_diff,
|
|
16
|
+
get_git_commits,
|
|
17
|
+
)
|
|
18
|
+
from openhands.sdk.git.git_diff import get_git_diff
|
|
19
|
+
from openhands.sdk.git.models import GitChange, GitCommitsPage, GitDiff
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
git_router = APIRouter(prefix="/git", tags=["Git"])
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_REF_QUERY_DESCRIPTION = (
|
|
27
|
+
"Optional git ref to diff against (e.g. 'HEAD' for git status-style "
|
|
28
|
+
"changes, or a commit hash). When omitted, the upstream/default branch "
|
|
29
|
+
"is auto-detected."
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
_COMMIT_QUERY_DESCRIPTION = (
|
|
33
|
+
"Optional commit SHA. When set, the diff is the change that commit "
|
|
34
|
+
"introduced (vs its first parent), read entirely from git objects so "
|
|
35
|
+
"deleted files still render. Mutually exclusive with 'ref'."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Hex-only so a path/query value can never reach git argv as an option
|
|
39
|
+
# (list-args protect against shell injection, not option injection).
|
|
40
|
+
_SHA_PATTERN = r"^[0-9a-fA-F]{4,64}$"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def _get_git_changes(path: str, ref: str | None) -> list[GitChange]:
|
|
44
|
+
"""Internal helper to get git changes for a given path."""
|
|
45
|
+
update_last_execution_time()
|
|
46
|
+
loop = asyncio.get_running_loop()
|
|
47
|
+
try:
|
|
48
|
+
return await loop.run_in_executor(
|
|
49
|
+
None, functools.partial(get_git_changes, Path(path), ref=ref)
|
|
50
|
+
)
|
|
51
|
+
except GitRepositoryError:
|
|
52
|
+
# A non-repo workspace has no git changes to report; respond with an
|
|
53
|
+
# empty list so the Changes tab can render normally instead of 500ing.
|
|
54
|
+
logger.debug("Path %s is not a git repository; returning no changes", path)
|
|
55
|
+
return []
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def _get_git_diff(path: str, ref: str | None) -> GitDiff:
|
|
59
|
+
"""Internal helper to get git diff for a given path."""
|
|
60
|
+
update_last_execution_time()
|
|
61
|
+
loop = asyncio.get_running_loop()
|
|
62
|
+
try:
|
|
63
|
+
return await loop.run_in_executor(
|
|
64
|
+
None, functools.partial(get_git_diff, Path(path), ref=ref)
|
|
65
|
+
)
|
|
66
|
+
except GitRepositoryError:
|
|
67
|
+
# Only collapse the not-a-repo case to an empty diff; file-level
|
|
68
|
+
# GitPathError (missing/oversize/outside-repo) stays a 500 so
|
|
69
|
+
# callers can distinguish it from "no changes".
|
|
70
|
+
logger.debug("Path %s is not in a git repository; returning empty diff", path)
|
|
71
|
+
return GitDiff(modified=None, original=None)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
async def _get_git_commits(path: str, limit: int) -> GitCommitsPage:
|
|
75
|
+
"""Internal helper to list commits for a given repo path."""
|
|
76
|
+
update_last_execution_time()
|
|
77
|
+
loop = asyncio.get_running_loop()
|
|
78
|
+
try:
|
|
79
|
+
return await loop.run_in_executor(
|
|
80
|
+
None, functools.partial(get_git_commits, Path(path), limit=limit)
|
|
81
|
+
)
|
|
82
|
+
except GitRepositoryError:
|
|
83
|
+
# A non-repo workspace has no commits to report; respond with an
|
|
84
|
+
# empty page so the commits section can render its empty state.
|
|
85
|
+
logger.debug("Path %s is not a git repository; returning no commits", path)
|
|
86
|
+
return GitCommitsPage(commits=[], has_more=False)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async def _get_commit_changes(path: str, sha: str) -> list[GitChange]:
|
|
90
|
+
"""Internal helper to get the files changed by one commit."""
|
|
91
|
+
update_last_execution_time()
|
|
92
|
+
loop = asyncio.get_running_loop()
|
|
93
|
+
try:
|
|
94
|
+
return await loop.run_in_executor(
|
|
95
|
+
None, functools.partial(get_commit_changes, Path(path), sha)
|
|
96
|
+
)
|
|
97
|
+
except GitRepositoryError:
|
|
98
|
+
logger.debug("Path %s is not a git repository; returning no changes", path)
|
|
99
|
+
return []
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def _get_commit_file_diff(path: str, commit: str) -> GitDiff:
|
|
103
|
+
"""Internal helper to get one file's diff as changed by one commit."""
|
|
104
|
+
update_last_execution_time()
|
|
105
|
+
loop = asyncio.get_running_loop()
|
|
106
|
+
try:
|
|
107
|
+
return await loop.run_in_executor(
|
|
108
|
+
None, functools.partial(get_commit_file_diff, Path(path), commit)
|
|
109
|
+
)
|
|
110
|
+
except GitRepositoryError:
|
|
111
|
+
logger.debug("Path %s is not in a git repository; returning empty diff", path)
|
|
112
|
+
return GitDiff(modified=None, original=None)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@git_router.get("/changes")
|
|
116
|
+
async def git_changes_query(
|
|
117
|
+
path: str = Query(..., description="The git repository path"),
|
|
118
|
+
ref: str | None = Query(None, description=_REF_QUERY_DESCRIPTION),
|
|
119
|
+
) -> list[GitChange]:
|
|
120
|
+
"""Get git changes using query parameter (preferred method)."""
|
|
121
|
+
try:
|
|
122
|
+
return await _get_git_changes(path, ref)
|
|
123
|
+
except GitError as e:
|
|
124
|
+
# GitRepositoryError is already handled in the helper (returns []).
|
|
125
|
+
# Any remaining GitError subclass (e.g. GitCommandError) surfaces as
|
|
126
|
+
# 400 so the client can show an actionable error instead of an
|
|
127
|
+
# opaque 500.
|
|
128
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@git_router.get("/diff")
|
|
132
|
+
async def git_diff_query(
|
|
133
|
+
path: str = Query(..., description="The file path to get diff for"),
|
|
134
|
+
ref: str | None = Query(None, description=_REF_QUERY_DESCRIPTION),
|
|
135
|
+
commit: str | None = Query(
|
|
136
|
+
None, pattern=_SHA_PATTERN, description=_COMMIT_QUERY_DESCRIPTION
|
|
137
|
+
),
|
|
138
|
+
) -> GitDiff:
|
|
139
|
+
"""Get git diff using query parameter (preferred method)."""
|
|
140
|
+
if ref is not None and commit is not None:
|
|
141
|
+
raise HTTPException(
|
|
142
|
+
status_code=400, detail="'ref' and 'commit' are mutually exclusive"
|
|
143
|
+
)
|
|
144
|
+
try:
|
|
145
|
+
if commit is not None:
|
|
146
|
+
return await _get_commit_file_diff(path, commit)
|
|
147
|
+
return await _get_git_diff(path, ref)
|
|
148
|
+
except GitError as e:
|
|
149
|
+
# GitRepositoryError is already handled in the helpers (returns an
|
|
150
|
+
# empty diff). Any remaining GitError subclass (e.g. GitCommandError,
|
|
151
|
+
# GitPathError) surfaces as 400 so the client can show an actionable
|
|
152
|
+
# error instead of an opaque 500.
|
|
153
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@git_router.get("/commits")
|
|
157
|
+
async def git_commits_query(
|
|
158
|
+
path: str = Query(..., description="The git repository path"),
|
|
159
|
+
limit: int = Query(50, ge=1, le=200, description="Maximum commits to return"),
|
|
160
|
+
) -> GitCommitsPage:
|
|
161
|
+
"""List the repository's most recent commits, newest first."""
|
|
162
|
+
try:
|
|
163
|
+
return await _get_git_commits(path, limit)
|
|
164
|
+
except GitError as e:
|
|
165
|
+
# GitRepositoryError is already handled in the helper (returns an
|
|
166
|
+
# empty page). Any remaining GitError subclass surfaces as 400.
|
|
167
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@git_router.get("/commits/{sha}/changes")
|
|
171
|
+
async def git_commit_changes_query(
|
|
172
|
+
sha: str = PathParam(..., pattern=_SHA_PATTERN, description="Commit SHA"),
|
|
173
|
+
path: str = Query(..., description="The git repository path"),
|
|
174
|
+
) -> list[GitChange]:
|
|
175
|
+
"""Get the files changed by a single commit (vs its first parent)."""
|
|
176
|
+
try:
|
|
177
|
+
return await _get_commit_changes(path, sha)
|
|
178
|
+
except GitError as e:
|
|
179
|
+
# GitRepositoryError is already handled in the helper (returns []).
|
|
180
|
+
# An unknown/unresolvable sha raises GitCommandError -> 400.
|
|
181
|
+
raise HTTPException(status_code=400, detail=str(e))
|
{openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/llm_router.py
RENAMED
|
@@ -138,9 +138,10 @@ async def list_models(
|
|
|
138
138
|
models = sorted(set(all_models))
|
|
139
139
|
else:
|
|
140
140
|
filtered_models = []
|
|
141
|
+
verified_provider_models = set(VERIFIED_MODELS.get(provider, ()))
|
|
141
142
|
for model in all_models:
|
|
142
|
-
model_provider,
|
|
143
|
-
if model_provider == provider:
|
|
143
|
+
model_provider, _, _ = _extract_model_and_provider(model)
|
|
144
|
+
if model_provider == provider or model in verified_provider_models:
|
|
144
145
|
filtered_models.append(model)
|
|
145
146
|
models = sorted(set(filtered_models))
|
|
146
147
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-agent-server
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.35.0
|
|
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
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
"""Git router for OpenHands SDK."""
|
|
2
|
-
|
|
3
|
-
import asyncio
|
|
4
|
-
import functools
|
|
5
|
-
import logging
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
from fastapi import APIRouter, HTTPException, Query
|
|
9
|
-
|
|
10
|
-
from openhands.agent_server.server_details_router import update_last_execution_time
|
|
11
|
-
from openhands.sdk.git.exceptions import GitError, GitRepositoryError
|
|
12
|
-
from openhands.sdk.git.git_changes import get_git_changes
|
|
13
|
-
from openhands.sdk.git.git_diff import get_git_diff
|
|
14
|
-
from openhands.sdk.git.models import GitChange, GitDiff
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
git_router = APIRouter(prefix="/git", tags=["Git"])
|
|
18
|
-
logger = logging.getLogger(__name__)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
_REF_QUERY_DESCRIPTION = (
|
|
22
|
-
"Optional git ref to diff against (e.g. 'HEAD' for git status-style "
|
|
23
|
-
"changes, or a commit hash). When omitted, the upstream/default branch "
|
|
24
|
-
"is auto-detected."
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
async def _get_git_changes(path: str, ref: str | None) -> list[GitChange]:
|
|
29
|
-
"""Internal helper to get git changes for a given path."""
|
|
30
|
-
update_last_execution_time()
|
|
31
|
-
loop = asyncio.get_running_loop()
|
|
32
|
-
try:
|
|
33
|
-
return await loop.run_in_executor(
|
|
34
|
-
None, functools.partial(get_git_changes, Path(path), ref=ref)
|
|
35
|
-
)
|
|
36
|
-
except GitRepositoryError:
|
|
37
|
-
# A non-repo workspace has no git changes to report; respond with an
|
|
38
|
-
# empty list so the Changes tab can render normally instead of 500ing.
|
|
39
|
-
logger.debug("Path %s is not a git repository; returning no changes", path)
|
|
40
|
-
return []
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
async def _get_git_diff(path: str, ref: str | None) -> GitDiff:
|
|
44
|
-
"""Internal helper to get git diff for a given path."""
|
|
45
|
-
update_last_execution_time()
|
|
46
|
-
loop = asyncio.get_running_loop()
|
|
47
|
-
try:
|
|
48
|
-
return await loop.run_in_executor(
|
|
49
|
-
None, functools.partial(get_git_diff, Path(path), ref=ref)
|
|
50
|
-
)
|
|
51
|
-
except GitRepositoryError:
|
|
52
|
-
# Only collapse the not-a-repo case to an empty diff; file-level
|
|
53
|
-
# GitPathError (missing/oversize/outside-repo) stays a 500 so
|
|
54
|
-
# callers can distinguish it from "no changes".
|
|
55
|
-
logger.debug("Path %s is not in a git repository; returning empty diff", path)
|
|
56
|
-
return GitDiff(modified=None, original=None)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@git_router.get("/changes")
|
|
60
|
-
async def git_changes_query(
|
|
61
|
-
path: str = Query(..., description="The git repository path"),
|
|
62
|
-
ref: str | None = Query(None, description=_REF_QUERY_DESCRIPTION),
|
|
63
|
-
) -> list[GitChange]:
|
|
64
|
-
"""Get git changes using query parameter (preferred method)."""
|
|
65
|
-
try:
|
|
66
|
-
return await _get_git_changes(path, ref)
|
|
67
|
-
except GitError as e:
|
|
68
|
-
# GitRepositoryError is already handled in the helper (returns []).
|
|
69
|
-
# Any remaining GitError subclass (e.g. GitCommandError) surfaces as
|
|
70
|
-
# 400 so the client can show an actionable error instead of an
|
|
71
|
-
# opaque 500.
|
|
72
|
-
raise HTTPException(status_code=400, detail=str(e))
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
@git_router.get("/diff")
|
|
76
|
-
async def git_diff_query(
|
|
77
|
-
path: str = Query(..., description="The file path to get diff for"),
|
|
78
|
-
ref: str | None = Query(None, description=_REF_QUERY_DESCRIPTION),
|
|
79
|
-
) -> GitDiff:
|
|
80
|
-
"""Get git diff using query parameter (preferred method)."""
|
|
81
|
-
try:
|
|
82
|
-
return await _get_git_diff(path, ref)
|
|
83
|
-
except GitError as e:
|
|
84
|
-
# GitRepositoryError is already handled in the helper (returns an
|
|
85
|
-
# empty diff). Any remaining GitError subclass (e.g. GitCommandError,
|
|
86
|
-
# GitPathError) surfaces as 400 so the client can show an actionable
|
|
87
|
-
# error instead of an opaque 500.
|
|
88
|
-
raise HTTPException(status_code=400, detail=str(e))
|
{openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/__init__.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/api.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/config.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
|
{openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/env_parser.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
|
{openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/mcp_router.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/middleware.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/openapi.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/pub_sub.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/sockets.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.34.0 → openhands_agent_server-1.35.0}/openhands/agent_server/utils.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
|