openhands-agent-server 1.40.0__tar.gz → 1.40.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.40.0 → openhands_agent_server-1.40.1}/PKG-INFO +1 -1
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/_secrets_exposure.py +19 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/agent_profiles_router.py +16 -33
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/config.py +17 -2
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/conversation_service.py +147 -34
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/mcp_oauth_store.py +4 -1
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/profiles_router.py +7 -25
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/settings_router.py +43 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands_agent_server.egg-info/PKG-INFO +1 -1
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/pyproject.toml +1 -1
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/__init__.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/__main__.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/_secret_redaction.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/api.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/auth_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/bash_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/bash_service.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/conversation_lease.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/conversation_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/credential_binding.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/dependencies.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/desktop_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/desktop_service.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/docker/Dockerfile +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/docker/build.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/docker/wallpaper.svg +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/env_parser.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/event_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/event_service.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/file_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/git_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/hooks_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/hooks_service.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/init_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/llm_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/logging_config.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/mcp_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/middleware.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/models.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/openai/__init__.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/openai/models.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/openai/router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/openai/service.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/openapi.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/persistence/__init__.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/persistence/models.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/persistence/store.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/plugins_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/plugins_service.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/pub_sub.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/py.typed +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/server_details_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/skills_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/skills_service.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/sockets.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/sub_agents_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/telemetry/__init__.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/telemetry/factory.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/telemetry/http_exporter.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/telemetry/models.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/telemetry/policy.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/telemetry/posthog_exporter.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/telemetry/sanitizer.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/telemetry/service.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/telemetry/sink.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/telemetry/subscriber.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/tool_preload_service.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/tool_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/utils.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/vscode_extensions/openhands-settings/extension.js +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/vscode_extensions/openhands-settings/package.json +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/vscode_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/vscode_service.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/workspace_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/workspaces_router.py +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands_agent_server.egg-info/SOURCES.txt +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands_agent_server.egg-info/dependency_links.txt +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands_agent_server.egg-info/entry_points.txt +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands_agent_server.egg-info/requires.txt +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands_agent_server.egg-info/top_level.txt +0 -0
- {openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-agent-server
|
|
3
|
-
Version: 1.40.
|
|
3
|
+
Version: 1.40.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
|
|
@@ -120,3 +120,22 @@ def translate_missing_cipher() -> Iterator[None]:
|
|
|
120
120
|
),
|
|
121
121
|
)
|
|
122
122
|
raise
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@contextmanager
|
|
126
|
+
def store_errors() -> Iterator[None]:
|
|
127
|
+
"""Map profile-store errors (``LLMProfileStore``/``AgentProfileStore``) to
|
|
128
|
+
HTTP responses. Shared by the settings, profiles, and agent-profiles
|
|
129
|
+
routers."""
|
|
130
|
+
try:
|
|
131
|
+
yield
|
|
132
|
+
except TimeoutError:
|
|
133
|
+
raise HTTPException(
|
|
134
|
+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
135
|
+
detail="Profile store is busy. Please retry.",
|
|
136
|
+
)
|
|
137
|
+
except ValueError as e:
|
|
138
|
+
raise HTTPException(
|
|
139
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
140
|
+
detail=str(e),
|
|
141
|
+
)
|
|
@@ -12,14 +12,16 @@ MCP references and returns :class:`~openhands.sdk.profiles.AgentProfileDiagnosti
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
import asyncio
|
|
15
|
-
from collections.abc import Iterator
|
|
16
|
-
from contextlib import contextmanager
|
|
17
15
|
from typing import Annotated, Any
|
|
18
16
|
|
|
19
17
|
from fastapi import APIRouter, HTTPException, Path, Request, status
|
|
20
18
|
from pydantic import BaseModel, Field, ValidationError
|
|
21
19
|
|
|
22
|
-
from openhands.agent_server._secrets_exposure import
|
|
20
|
+
from openhands.agent_server._secrets_exposure import (
|
|
21
|
+
get_cipher,
|
|
22
|
+
get_config,
|
|
23
|
+
store_errors,
|
|
24
|
+
)
|
|
23
25
|
from openhands.agent_server.persistence import (
|
|
24
26
|
PersistedSettings,
|
|
25
27
|
get_agent_profile_store,
|
|
@@ -105,25 +107,6 @@ class RenameAgentProfileRequest(BaseModel):
|
|
|
105
107
|
)
|
|
106
108
|
|
|
107
109
|
|
|
108
|
-
@contextmanager
|
|
109
|
-
def _store_errors() -> Iterator[None]:
|
|
110
|
-
"""Map ``AgentProfileStore`` errors to HTTP responses.
|
|
111
|
-
|
|
112
|
-
Mirrors ``profiles_router._store_errors``: ``TimeoutError`` and
|
|
113
|
-
``ValueError`` only. ``FileNotFoundError`` / ``FileExistsError`` are handled
|
|
114
|
-
inline per-endpoint so each gets a clean, resource-specific message.
|
|
115
|
-
"""
|
|
116
|
-
try:
|
|
117
|
-
yield
|
|
118
|
-
except TimeoutError:
|
|
119
|
-
raise HTTPException(
|
|
120
|
-
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
121
|
-
detail="Agent profile store is busy. Please retry.",
|
|
122
|
-
)
|
|
123
|
-
except ValueError as e:
|
|
124
|
-
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
125
|
-
|
|
126
|
-
|
|
127
110
|
def _llm_has_real_config(llm: LLM) -> bool:
|
|
128
111
|
"""True when ``llm`` carries real, user-provided configuration.
|
|
129
112
|
|
|
@@ -173,7 +156,7 @@ def _seed_default_llm_profile(llm: LLM, cipher: Cipher | None) -> str:
|
|
|
173
156
|
silently clobber it.
|
|
174
157
|
"""
|
|
175
158
|
llm_store = get_llm_profile_store()
|
|
176
|
-
with
|
|
159
|
+
with store_errors():
|
|
177
160
|
try:
|
|
178
161
|
llm_store.load(SEED_PROFILE_NAME, cipher=cipher)
|
|
179
162
|
return SEED_PROFILE_NAME
|
|
@@ -227,7 +210,7 @@ def _seed_default_profile(
|
|
|
227
210
|
The lock spans empty-check + save + pointer write so concurrent first
|
|
228
211
|
requests seed exactly once and the pointer matches the persisted id.
|
|
229
212
|
"""
|
|
230
|
-
with
|
|
213
|
+
with store_errors(), store.lock():
|
|
231
214
|
# Double-checked under the lock: a concurrent first request may have
|
|
232
215
|
# already seeded (the outer emptiness check in the list endpoint is
|
|
233
216
|
# unlocked).
|
|
@@ -261,7 +244,7 @@ def _seed_default_profile(
|
|
|
261
244
|
|
|
262
245
|
def _summary_id_for_name(store: AgentProfileStore, name: str) -> str | None:
|
|
263
246
|
"""Return the stable id of the profile stored under ``name``, if present."""
|
|
264
|
-
with
|
|
247
|
+
with store_errors():
|
|
265
248
|
for summary in store.list_summaries():
|
|
266
249
|
if summary.get("name") == name:
|
|
267
250
|
sid = summary.get("id")
|
|
@@ -282,14 +265,14 @@ async def list_agent_profiles(request: Request) -> AgentProfileListResponse:
|
|
|
282
265
|
settings = settings_store.load() or PersistedSettings()
|
|
283
266
|
|
|
284
267
|
store = get_agent_profile_store()
|
|
285
|
-
with
|
|
268
|
+
with store_errors():
|
|
286
269
|
existing = store.list()
|
|
287
270
|
|
|
288
271
|
if not existing and settings.active_agent_profile_id is None:
|
|
289
272
|
_seed_default_profile(store, request, settings, get_cipher(request))
|
|
290
273
|
settings = settings_store.load() or settings
|
|
291
274
|
|
|
292
|
-
with
|
|
275
|
+
with store_errors():
|
|
293
276
|
summaries = store.list_summaries()
|
|
294
277
|
|
|
295
278
|
return AgentProfileListResponse(
|
|
@@ -308,7 +291,7 @@ async def get_agent_profile(name: ProfileName) -> AgentProfileDetailResponse:
|
|
|
308
291
|
"""
|
|
309
292
|
store = get_agent_profile_store()
|
|
310
293
|
try:
|
|
311
|
-
with
|
|
294
|
+
with store_errors():
|
|
312
295
|
profile = store.load(name)
|
|
313
296
|
except FileNotFoundError:
|
|
314
297
|
raise HTTPException(
|
|
@@ -361,7 +344,7 @@ async def save_agent_profile(
|
|
|
361
344
|
# holds the store lock across read + mint + save so two concurrent creates
|
|
362
345
|
# of the same new name can't both mint an id and clobber each other.
|
|
363
346
|
try:
|
|
364
|
-
with
|
|
347
|
+
with store_errors():
|
|
365
348
|
save_profile_preserving_identity(
|
|
366
349
|
store, profile, max_profiles=MAX_AGENT_PROFILES
|
|
367
350
|
)
|
|
@@ -392,7 +375,7 @@ async def delete_agent_profile(
|
|
|
392
375
|
store = get_agent_profile_store()
|
|
393
376
|
deleted_id = _summary_id_for_name(store, name)
|
|
394
377
|
|
|
395
|
-
with
|
|
378
|
+
with store_errors():
|
|
396
379
|
store.delete(name)
|
|
397
380
|
|
|
398
381
|
if deleted_id is not None:
|
|
@@ -428,7 +411,7 @@ async def rename_agent_profile(
|
|
|
428
411
|
"""
|
|
429
412
|
store = get_agent_profile_store()
|
|
430
413
|
try:
|
|
431
|
-
with
|
|
414
|
+
with store_errors():
|
|
432
415
|
store.rename(name, body.new_name)
|
|
433
416
|
except FileNotFoundError:
|
|
434
417
|
raise HTTPException(
|
|
@@ -462,7 +445,7 @@ async def activate_agent_profile(
|
|
|
462
445
|
creation-time-only contract). Returns 404 if no stored profile has that id.
|
|
463
446
|
"""
|
|
464
447
|
store = get_agent_profile_store()
|
|
465
|
-
with
|
|
448
|
+
with store_errors():
|
|
466
449
|
known_ids = {
|
|
467
450
|
str(s["id"]) for s in store.list_summaries() if s.get("id") is not None
|
|
468
451
|
}
|
|
@@ -515,7 +498,7 @@ async def materialize_agent_profile(
|
|
|
515
498
|
"""
|
|
516
499
|
store = get_agent_profile_store()
|
|
517
500
|
try:
|
|
518
|
-
with
|
|
501
|
+
with store_errors():
|
|
519
502
|
profile = store.load(name)
|
|
520
503
|
except FileNotFoundError:
|
|
521
504
|
raise HTTPException(
|
{openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/config.py
RENAMED
|
@@ -106,11 +106,26 @@ class WebhookSpec(BaseModel):
|
|
|
106
106
|
default=1000,
|
|
107
107
|
ge=1,
|
|
108
108
|
description=(
|
|
109
|
-
"Upper bound on the number of events buffered for delivery.
|
|
110
|
-
"downstream is failing and events are re-queued for retry, the oldest "
|
|
109
|
+
"Upper bound on the number of events buffered for delivery. The oldest "
|
|
111
110
|
"events are dropped past this bound to prevent unbounded memory growth."
|
|
112
111
|
),
|
|
113
112
|
)
|
|
113
|
+
max_batch_bytes: int = Field(
|
|
114
|
+
default=5 * 1024 * 1024,
|
|
115
|
+
ge=1,
|
|
116
|
+
description=(
|
|
117
|
+
"Upper bound on the serialized size of each webhook request. A single "
|
|
118
|
+
"event larger than this limit is sent by itself."
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
max_queue_bytes: int = Field(
|
|
122
|
+
default=50 * 1024 * 1024,
|
|
123
|
+
ge=1,
|
|
124
|
+
description=(
|
|
125
|
+
"Upper bound on the serialized size of events buffered for delivery. "
|
|
126
|
+
"The oldest events are dropped when the queue exceeds this bound."
|
|
127
|
+
),
|
|
128
|
+
)
|
|
114
129
|
|
|
115
130
|
|
|
116
131
|
TelemetryExporterKind = Literal["none", "posthog", "http"]
|
|
@@ -50,6 +50,7 @@ from openhands.sdk import LLM, AgentContext, Event, Message
|
|
|
50
50
|
from openhands.sdk.agent import ACPAgent
|
|
51
51
|
from openhands.sdk.agent.acp_file_credentials import CODEX_AUTH_SECRET_NAME
|
|
52
52
|
from openhands.sdk.agent.base import AgentBase
|
|
53
|
+
from openhands.sdk.conversation.impl.local_conversation import LocalConversation
|
|
53
54
|
from openhands.sdk.conversation.persistence_const import BASE_STATE
|
|
54
55
|
from openhands.sdk.conversation.state import (
|
|
55
56
|
ConversationExecutionStatus,
|
|
@@ -65,6 +66,7 @@ from openhands.sdk.event.conversation_state import ConversationStateUpdateEvent
|
|
|
65
66
|
from openhands.sdk.git.exceptions import GitCommandError, GitRepositoryError
|
|
66
67
|
from openhands.sdk.git.utils import run_git_command, validate_git_repository
|
|
67
68
|
from openhands.sdk.mcp.utils import MCPToolProvider
|
|
69
|
+
from openhands.sdk.observability import OPERATION_METADATA_KEY, observe
|
|
68
70
|
from openhands.sdk.tool import BROWSER_TOOL_NAME, Tool, is_tool_usable
|
|
69
71
|
from openhands.sdk.tool.client_tool import register_client_tools
|
|
70
72
|
from openhands.sdk.utils.cipher import Cipher
|
|
@@ -2193,6 +2195,22 @@ class _EventSubscriber(Subscriber):
|
|
|
2193
2195
|
update_last_execution_time()
|
|
2194
2196
|
|
|
2195
2197
|
|
|
2198
|
+
@observe(
|
|
2199
|
+
name="conversation.generate_title",
|
|
2200
|
+
ignore_inputs=["conversation", "llm"],
|
|
2201
|
+
metadata={OPERATION_METADATA_KEY: "title_generation"},
|
|
2202
|
+
)
|
|
2203
|
+
def _generate_title_traced(
|
|
2204
|
+
# Unused, but must stay first and positional: ``observe`` re-attaches the
|
|
2205
|
+
# root span it carries, and this runs on a context-less executor thread.
|
|
2206
|
+
conversation: LocalConversation | None, # noqa: ARG001
|
|
2207
|
+
message: str,
|
|
2208
|
+
llm: LLM | None,
|
|
2209
|
+
max_length: int,
|
|
2210
|
+
) -> str:
|
|
2211
|
+
return generate_title_from_message(message, llm, max_length)
|
|
2212
|
+
|
|
2213
|
+
|
|
2196
2214
|
@dataclass
|
|
2197
2215
|
class AutoTitleSubscriber(Subscriber):
|
|
2198
2216
|
service: EventService
|
|
@@ -2215,9 +2233,9 @@ class AutoTitleSubscriber(Subscriber):
|
|
|
2215
2233
|
# Precedence: title_llm_profile (if configured and loads) → agent.llm →
|
|
2216
2234
|
# truncation. This keeps auto-titling non-breaking for consumers who
|
|
2217
2235
|
# don't configure title_llm_profile.
|
|
2236
|
+
conversation = self.service._conversation
|
|
2218
2237
|
title_llm = self._load_title_llm()
|
|
2219
2238
|
if title_llm is None:
|
|
2220
|
-
conversation = self.service._conversation
|
|
2221
2239
|
title_llm = conversation.agent.llm if conversation else None
|
|
2222
2240
|
|
|
2223
2241
|
async def _generate_and_save() -> None:
|
|
@@ -2225,7 +2243,8 @@ class AutoTitleSubscriber(Subscriber):
|
|
|
2225
2243
|
loop = asyncio.get_running_loop()
|
|
2226
2244
|
title = await loop.run_in_executor(
|
|
2227
2245
|
None,
|
|
2228
|
-
|
|
2246
|
+
_generate_title_traced,
|
|
2247
|
+
conversation,
|
|
2229
2248
|
message_text,
|
|
2230
2249
|
title_llm,
|
|
2231
2250
|
50,
|
|
@@ -2278,6 +2297,11 @@ class WebhookSubscriber(Subscriber):
|
|
|
2278
2297
|
session_api_key: str | None = None
|
|
2279
2298
|
queue: list[Event] = field(default_factory=list)
|
|
2280
2299
|
_flush_timer: asyncio.Task | None = field(default=None, init=False)
|
|
2300
|
+
_post_lock: asyncio.Lock = field(default_factory=asyncio.Lock, init=False)
|
|
2301
|
+
_queue_sizes: list[int] = field(default_factory=list, init=False)
|
|
2302
|
+
_queue_bytes: int = field(default=0, init=False)
|
|
2303
|
+
_dropped_events: int = field(default=0, init=False)
|
|
2304
|
+
_closed: bool = field(default=False, init=False)
|
|
2281
2305
|
# Per-instance sleep seam so tests override delays without patching the
|
|
2282
2306
|
# global asyncio.sleep. default_factory (not default) keeps it an instance
|
|
2283
2307
|
# attribute, else the function would be descriptor-bound as a method.
|
|
@@ -2287,17 +2311,24 @@ class WebhookSubscriber(Subscriber):
|
|
|
2287
2311
|
|
|
2288
2312
|
async def __call__(self, event: Event):
|
|
2289
2313
|
"""Add event to queue and post to webhook when buffer size is reached."""
|
|
2290
|
-
self.
|
|
2314
|
+
self._enqueue(event)
|
|
2291
2315
|
|
|
2292
|
-
if
|
|
2316
|
+
if (
|
|
2317
|
+
len(self.queue) >= self.spec.event_buffer_size
|
|
2318
|
+
or self._queue_bytes >= self.spec.max_batch_bytes
|
|
2319
|
+
):
|
|
2320
|
+
if self._post_lock.locked():
|
|
2321
|
+
self._start_flush_timer()
|
|
2322
|
+
return
|
|
2293
2323
|
# Cancel timer since we're flushing due to buffer size
|
|
2294
2324
|
self._cancel_flush_timer()
|
|
2295
2325
|
await self._post_events()
|
|
2296
|
-
elif
|
|
2297
|
-
self.
|
|
2326
|
+
elif self.queue:
|
|
2327
|
+
self._start_flush_timer()
|
|
2298
2328
|
|
|
2299
2329
|
async def close(self):
|
|
2300
2330
|
"""Post any remaining items in the queue to the webhook."""
|
|
2331
|
+
self._closed = True
|
|
2301
2332
|
# Cancel any pending flush timer
|
|
2302
2333
|
self._cancel_flush_timer()
|
|
2303
2334
|
|
|
@@ -2305,30 +2336,26 @@ class WebhookSubscriber(Subscriber):
|
|
|
2305
2336
|
await self._post_events()
|
|
2306
2337
|
|
|
2307
2338
|
async def _post_events(self):
|
|
2308
|
-
"""Post
|
|
2309
|
-
|
|
2310
|
-
|
|
2339
|
+
"""Post bounded batches serially until the queue is empty or a post fails."""
|
|
2340
|
+
async with self._post_lock:
|
|
2341
|
+
self._sync_queue_sizes()
|
|
2342
|
+
self._trim_queue()
|
|
2343
|
+
events_remaining = len(self.queue)
|
|
2344
|
+
while events_remaining:
|
|
2345
|
+
events_to_post, event_data = self._take_batch(events_remaining)
|
|
2346
|
+
if not await self._post_batch(event_data):
|
|
2347
|
+
self._requeue(events_to_post)
|
|
2348
|
+
return
|
|
2349
|
+
events_remaining -= len(events_to_post)
|
|
2311
2350
|
|
|
2312
|
-
|
|
2313
|
-
|
|
2351
|
+
async def _post_batch(self, event_data: list[dict[str, Any]]) -> bool:
|
|
2352
|
+
"""Post one serialized batch with retry logic."""
|
|
2314
2353
|
|
|
2315
2354
|
# Prepare headers
|
|
2316
2355
|
headers = self.spec.headers.copy()
|
|
2317
2356
|
if self.session_api_key:
|
|
2318
2357
|
headers["X-Session-API-Key"] = self.session_api_key
|
|
2319
2358
|
|
|
2320
|
-
# Convert events to a JSON-serializable format. mode="json" is required
|
|
2321
|
-
# so types like set and SecretStr become JSON-safe primitives; without
|
|
2322
|
-
# it httpx's encoder raises "Object of type set/SecretStr is not JSON
|
|
2323
|
-
# serializable", every retry fails identically, and the events are
|
|
2324
|
-
# dropped. (Mirrors ConversationWebhookSubscriber.post_conversation_info.)
|
|
2325
|
-
event_data = [
|
|
2326
|
-
event.model_dump(mode="json")
|
|
2327
|
-
if hasattr(event, "model_dump")
|
|
2328
|
-
else event.__dict__
|
|
2329
|
-
for event in events_to_post
|
|
2330
|
-
]
|
|
2331
|
-
|
|
2332
2359
|
# Construct events URL
|
|
2333
2360
|
events_url = (
|
|
2334
2361
|
f"{self.spec.base_url.rstrip('/')}/events/{self.conversation_id.hex}"
|
|
@@ -2350,7 +2377,7 @@ class WebhookSubscriber(Subscriber):
|
|
|
2350
2377
|
f"Successfully posted {len(event_data)} events "
|
|
2351
2378
|
f"to webhook {events_url}"
|
|
2352
2379
|
)
|
|
2353
|
-
return
|
|
2380
|
+
return True
|
|
2354
2381
|
except Exception as e:
|
|
2355
2382
|
logger.warning(f"Webhook post attempt {attempt + 1} failed: {e}")
|
|
2356
2383
|
if attempt < self.spec.num_retries:
|
|
@@ -2360,15 +2387,95 @@ class WebhookSubscriber(Subscriber):
|
|
|
2360
2387
|
f"Failed to post events to webhook {events_url} "
|
|
2361
2388
|
f"after {self.spec.num_retries + 1} attempts"
|
|
2362
2389
|
)
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2390
|
+
return False
|
|
2391
|
+
|
|
2392
|
+
@staticmethod
|
|
2393
|
+
def _event_data(event: Event) -> dict[str, Any]:
|
|
2394
|
+
# mode="json" makes types such as set and SecretStr JSON-safe.
|
|
2395
|
+
if hasattr(event, "model_dump"):
|
|
2396
|
+
return event.model_dump(mode="json")
|
|
2397
|
+
return event.__dict__
|
|
2398
|
+
|
|
2399
|
+
@classmethod
|
|
2400
|
+
def _event_size(cls, event: Event) -> int:
|
|
2401
|
+
return len(
|
|
2402
|
+
json.dumps(
|
|
2403
|
+
cls._event_data(event),
|
|
2404
|
+
ensure_ascii=False,
|
|
2405
|
+
separators=(",", ":"),
|
|
2406
|
+
allow_nan=False,
|
|
2407
|
+
).encode()
|
|
2408
|
+
)
|
|
2409
|
+
|
|
2410
|
+
def _sync_queue_sizes(self):
|
|
2411
|
+
"""Refresh size accounting after callers directly replace the public queue."""
|
|
2412
|
+
if len(self._queue_sizes) != len(self.queue):
|
|
2413
|
+
self._queue_sizes = [self._event_size(event) for event in self.queue]
|
|
2414
|
+
self._queue_bytes = sum(self._queue_sizes)
|
|
2415
|
+
|
|
2416
|
+
def _enqueue(self, event: Event):
|
|
2417
|
+
self._sync_queue_sizes()
|
|
2418
|
+
event_size = self._event_size(event)
|
|
2419
|
+
self.queue.append(event)
|
|
2420
|
+
self._queue_sizes.append(event_size)
|
|
2421
|
+
self._queue_bytes += event_size
|
|
2422
|
+
self._trim_queue()
|
|
2423
|
+
|
|
2424
|
+
def _requeue(self, events: list[Event]):
|
|
2425
|
+
sizes = [self._event_size(event) for event in events]
|
|
2426
|
+
self.queue[:0] = events
|
|
2427
|
+
self._queue_sizes[:0] = sizes
|
|
2428
|
+
self._queue_bytes += sum(sizes)
|
|
2429
|
+
self._trim_queue()
|
|
2430
|
+
|
|
2431
|
+
def _trim_queue(self):
|
|
2432
|
+
dropped = 0
|
|
2433
|
+
while self.queue and (
|
|
2434
|
+
len(self.queue) > self.spec.max_queue_size
|
|
2435
|
+
or self._queue_bytes > self.spec.max_queue_bytes
|
|
2436
|
+
):
|
|
2437
|
+
del self.queue[0]
|
|
2438
|
+
self._queue_bytes -= self._queue_sizes.pop(0)
|
|
2439
|
+
dropped += 1
|
|
2440
|
+
if dropped:
|
|
2441
|
+
previous_dropped = self._dropped_events
|
|
2442
|
+
self._dropped_events += dropped
|
|
2443
|
+
if (
|
|
2444
|
+
previous_dropped == 0
|
|
2445
|
+
or self._dropped_events // 100 > previous_dropped // 100
|
|
2446
|
+
):
|
|
2447
|
+
logger.warning(
|
|
2448
|
+
"Webhook queue exceeded its configured count or byte limit; "
|
|
2449
|
+
f"dropped {self._dropped_events} event(s) so far for conversation "
|
|
2450
|
+
f"{self.conversation_id.hex}."
|
|
2451
|
+
)
|
|
2452
|
+
|
|
2453
|
+
def _take_batch(self, max_events: int) -> tuple[list[Event], list[dict[str, Any]]]:
|
|
2454
|
+
self._sync_queue_sizes()
|
|
2455
|
+
batch_size = 2 # JSON array brackets
|
|
2456
|
+
batch_count = 0
|
|
2457
|
+
event_data: list[dict[str, Any]] = []
|
|
2458
|
+
|
|
2459
|
+
for event, event_size in zip(self.queue, self._queue_sizes, strict=True):
|
|
2460
|
+
next_size = batch_size + event_size + (1 if batch_count else 0)
|
|
2461
|
+
if batch_count and next_size > self.spec.max_batch_bytes:
|
|
2462
|
+
break
|
|
2463
|
+
event_data.append(self._event_data(event))
|
|
2464
|
+
batch_size = next_size
|
|
2465
|
+
batch_count += 1
|
|
2466
|
+
if batch_count >= min(self.spec.event_buffer_size, max_events):
|
|
2467
|
+
break
|
|
2468
|
+
|
|
2469
|
+
events = self.queue[:batch_count]
|
|
2470
|
+
del self.queue[:batch_count]
|
|
2471
|
+
removed_sizes = self._queue_sizes[:batch_count]
|
|
2472
|
+
del self._queue_sizes[:batch_count]
|
|
2473
|
+
self._queue_bytes -= sum(removed_sizes)
|
|
2474
|
+
return events, event_data
|
|
2475
|
+
|
|
2476
|
+
def _start_flush_timer(self):
|
|
2477
|
+
if not self._closed and not self._flush_timer:
|
|
2478
|
+
self._flush_timer = asyncio.create_task(self._flush_after_delay())
|
|
2372
2479
|
|
|
2373
2480
|
def _cancel_flush_timer(self):
|
|
2374
2481
|
"""Cancel the current flush timer if it exists."""
|
|
@@ -2378,16 +2485,22 @@ class WebhookSubscriber(Subscriber):
|
|
|
2378
2485
|
|
|
2379
2486
|
async def _flush_after_delay(self):
|
|
2380
2487
|
"""Wait for flush_delay seconds then flush events if any exist."""
|
|
2488
|
+
current_task = asyncio.current_task()
|
|
2489
|
+
should_reschedule = False
|
|
2381
2490
|
try:
|
|
2382
2491
|
await self._sleep(self.spec.flush_delay)
|
|
2383
2492
|
# Only flush if there are events in the queue
|
|
2384
2493
|
if self.queue:
|
|
2385
2494
|
await self._post_events()
|
|
2495
|
+
should_reschedule = bool(self.queue)
|
|
2386
2496
|
except asyncio.CancelledError:
|
|
2387
2497
|
# Timer was cancelled, which is expected behavior
|
|
2388
2498
|
pass
|
|
2389
2499
|
finally:
|
|
2390
|
-
self._flush_timer
|
|
2500
|
+
if self._flush_timer is current_task:
|
|
2501
|
+
self._flush_timer = None
|
|
2502
|
+
if should_reschedule:
|
|
2503
|
+
self._start_flush_timer()
|
|
2391
2504
|
|
|
2392
2505
|
|
|
2393
2506
|
@dataclass
|
|
@@ -24,7 +24,10 @@ from openhands.sdk.mcp.config import (
|
|
|
24
24
|
MCPOAuthTokenStorageField,
|
|
25
25
|
MCPServer,
|
|
26
26
|
)
|
|
27
|
-
from openhands.sdk.mcp.utils import
|
|
27
|
+
from openhands.sdk.mcp.utils import (
|
|
28
|
+
ToolsChangedCallback,
|
|
29
|
+
create_mcp_tools,
|
|
30
|
+
)
|
|
28
31
|
|
|
29
32
|
|
|
30
33
|
logger = get_logger(__name__)
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""HTTP endpoints for managing named LLM configurations (profiles)."""
|
|
2
2
|
|
|
3
|
-
from collections.abc import Iterator
|
|
4
|
-
from contextlib import contextmanager
|
|
5
3
|
from typing import Annotated, Any
|
|
6
4
|
|
|
7
5
|
from fastapi import APIRouter, HTTPException, Path, Request, status
|
|
@@ -13,6 +11,7 @@ from openhands.agent_server._secrets_exposure import (
|
|
|
13
11
|
get_cipher,
|
|
14
12
|
get_config,
|
|
15
13
|
parse_expose_secrets_header,
|
|
14
|
+
store_errors,
|
|
16
15
|
translate_missing_cipher,
|
|
17
16
|
)
|
|
18
17
|
from openhands.agent_server.persistence import (
|
|
@@ -88,23 +87,6 @@ class RenameProfileRequest(BaseModel):
|
|
|
88
87
|
)
|
|
89
88
|
|
|
90
89
|
|
|
91
|
-
@contextmanager
|
|
92
|
-
def _store_errors() -> Iterator[None]:
|
|
93
|
-
"""Map ``LLMProfileStore`` errors to HTTP responses."""
|
|
94
|
-
try:
|
|
95
|
-
yield
|
|
96
|
-
except TimeoutError:
|
|
97
|
-
raise HTTPException(
|
|
98
|
-
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
99
|
-
detail="Profile store is busy. Please retry.",
|
|
100
|
-
)
|
|
101
|
-
except ValueError as e:
|
|
102
|
-
raise HTTPException(
|
|
103
|
-
status_code=status.HTTP_400_BAD_REQUEST,
|
|
104
|
-
detail=str(e),
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
|
|
108
90
|
def _has_api_key(llm: LLM) -> bool:
|
|
109
91
|
if not isinstance(llm.api_key, SecretStr):
|
|
110
92
|
return False
|
|
@@ -141,7 +123,7 @@ async def list_profiles(request: Request) -> ProfileListResponse:
|
|
|
141
123
|
settings = settings_store.load() or PersistedSettings()
|
|
142
124
|
|
|
143
125
|
store = get_llm_profile_store()
|
|
144
|
-
with
|
|
126
|
+
with store_errors():
|
|
145
127
|
summaries = store.list_summaries()
|
|
146
128
|
|
|
147
129
|
return ProfileListResponse(
|
|
@@ -164,7 +146,7 @@ async def get_profile(request: Request, name: ProfileName) -> ProfileDetailRespo
|
|
|
164
146
|
|
|
165
147
|
store = get_llm_profile_store()
|
|
166
148
|
try:
|
|
167
|
-
with
|
|
149
|
+
with store_errors():
|
|
168
150
|
llm = store.load(name, cipher=cipher)
|
|
169
151
|
except FileNotFoundError:
|
|
170
152
|
raise HTTPException(
|
|
@@ -208,7 +190,7 @@ async def save_profile(
|
|
|
208
190
|
llm = decrypt_incoming_llm_secrets(body.llm, cipher) if cipher else body.llm
|
|
209
191
|
store = get_llm_profile_store()
|
|
210
192
|
try:
|
|
211
|
-
with
|
|
193
|
+
with store_errors():
|
|
212
194
|
store.save(
|
|
213
195
|
name,
|
|
214
196
|
llm,
|
|
@@ -241,7 +223,7 @@ async def delete_profile(
|
|
|
241
223
|
store = get_llm_profile_store()
|
|
242
224
|
agent_store = get_agent_profile_store()
|
|
243
225
|
try:
|
|
244
|
-
with
|
|
226
|
+
with store_errors():
|
|
245
227
|
delete_llm_profile(agent_store, store, name)
|
|
246
228
|
except ProfileReferenced as e:
|
|
247
229
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e))
|
|
@@ -269,7 +251,7 @@ async def rename_profile(
|
|
|
269
251
|
store = get_llm_profile_store()
|
|
270
252
|
agent_store = get_agent_profile_store()
|
|
271
253
|
try:
|
|
272
|
-
with
|
|
254
|
+
with store_errors():
|
|
273
255
|
rename_llm_profile(agent_store, store, name, body.new_name)
|
|
274
256
|
except FileNotFoundError:
|
|
275
257
|
raise HTTPException(
|
|
@@ -325,7 +307,7 @@ async def activate_profile(
|
|
|
325
307
|
# Load the profile
|
|
326
308
|
profile_store = get_llm_profile_store()
|
|
327
309
|
try:
|
|
328
|
-
with
|
|
310
|
+
with store_errors():
|
|
329
311
|
llm = profile_store.load(name, cipher=cipher)
|
|
330
312
|
except FileNotFoundError:
|
|
331
313
|
raise HTTPException(
|
|
@@ -7,13 +7,16 @@ from pydantic import ValidationError
|
|
|
7
7
|
|
|
8
8
|
from openhands.agent_server._secrets_exposure import (
|
|
9
9
|
build_expose_context,
|
|
10
|
+
get_cipher,
|
|
10
11
|
get_config,
|
|
11
12
|
parse_expose_secrets_header,
|
|
13
|
+
store_errors,
|
|
12
14
|
translate_missing_cipher,
|
|
13
15
|
)
|
|
14
16
|
from openhands.agent_server.persistence import (
|
|
15
17
|
SECRET_NAME_PATTERN,
|
|
16
18
|
PersistedSettings,
|
|
19
|
+
get_llm_profile_store,
|
|
17
20
|
get_secrets_store,
|
|
18
21
|
get_settings_store,
|
|
19
22
|
)
|
|
@@ -179,6 +182,10 @@ async def update_settings(
|
|
|
179
182
|
|
|
180
183
|
Accepts ``agent_settings_diff``, ``conversation_settings_diff``,
|
|
181
184
|
``misc_settings_diff``, and/or ``active_profile`` for incremental updates.
|
|
185
|
+
Setting ``active_profile`` loads and applies that profile's LLM, same as
|
|
186
|
+
``POST /api/profiles/{name}/activate``, unless ``agent_settings_diff.llm``
|
|
187
|
+
is also given.
|
|
188
|
+
|
|
182
189
|
The three ``*_settings_diff`` fields are deep-merged; nested objects merge
|
|
183
190
|
recursively, and a ``null`` value **inside a nested map deletes that entry**
|
|
184
191
|
— the "unset" primitive that lets a client remove a single map key without
|
|
@@ -226,11 +233,47 @@ async def update_settings(
|
|
|
226
233
|
)
|
|
227
234
|
|
|
228
235
|
|
|
236
|
+
def _resolve_active_profile_llm(
|
|
237
|
+
request: Request, update_data: SettingsUpdatePayload
|
|
238
|
+
) -> SettingsUpdatePayload:
|
|
239
|
+
"""Fold the named profile's LLM into ``agent_settings_diff`` unless the
|
|
240
|
+
caller already gave one explicitly. Mirrors ``/activate``."""
|
|
241
|
+
profile_name = update_data.get("active_profile")
|
|
242
|
+
agent_diff = update_data.get("agent_settings_diff")
|
|
243
|
+
explicit_llm_diff = isinstance(agent_diff, dict) and "llm" in agent_diff
|
|
244
|
+
if not profile_name or explicit_llm_diff:
|
|
245
|
+
return update_data
|
|
246
|
+
|
|
247
|
+
cipher = get_cipher(request)
|
|
248
|
+
profile_store = get_llm_profile_store()
|
|
249
|
+
try:
|
|
250
|
+
with store_errors():
|
|
251
|
+
llm = profile_store.load(profile_name, cipher=cipher)
|
|
252
|
+
except FileNotFoundError:
|
|
253
|
+
raise HTTPException(
|
|
254
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
255
|
+
detail=f"Profile '{profile_name}' not found",
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
return cast(
|
|
259
|
+
SettingsUpdatePayload,
|
|
260
|
+
{
|
|
261
|
+
**update_data,
|
|
262
|
+
"agent_settings_diff": {
|
|
263
|
+
**(agent_diff if isinstance(agent_diff, dict) else {}),
|
|
264
|
+
"llm": llm.model_dump(mode="json", context={"expose_secrets": True}),
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
|
|
229
270
|
def _apply_settings_update(
|
|
230
271
|
request: Request,
|
|
231
272
|
update_data: SettingsUpdatePayload,
|
|
232
273
|
before_update: Callable[[PersistedSettings], None] | None = None,
|
|
233
274
|
) -> SettingsResponse:
|
|
275
|
+
update_data = _resolve_active_profile_llm(request, update_data)
|
|
276
|
+
|
|
234
277
|
# Apply updates atomically with file locking
|
|
235
278
|
def apply_update(settings: PersistedSettings) -> PersistedSettings:
|
|
236
279
|
if before_update is not None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-agent-server
|
|
3
|
-
Version: 1.40.
|
|
3
|
+
Version: 1.40.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
|
{openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/__init__.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/api.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
|
{openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/env_parser.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/git_router.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/llm_router.py
RENAMED
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/mcp_router.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/middleware.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/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.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/openapi.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/pub_sub.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/openhands/agent_server/sockets.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
|
{openhands_agent_server-1.40.0 → openhands_agent_server-1.40.1}/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
|