openhands-agent-server 1.32.0__tar.gz → 1.34.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.32.0 → openhands_agent_server-1.34.0}/PKG-INFO +1 -1
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/agent_profiles_router.py +77 -52
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/conversation_router.py +0 -6
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/conversation_service.py +25 -12
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/skills_service.py +11 -30
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands_agent_server.egg-info/PKG-INFO +1 -1
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/pyproject.toml +1 -1
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/__init__.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/__main__.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/_secrets_exposure.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/api.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/auth_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/bash_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/bash_service.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/config.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/conversation_lease.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/dependencies.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/desktop_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/desktop_service.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/docker/Dockerfile +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/docker/build.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/docker/wallpaper.svg +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/env_parser.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/event_compat.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/event_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/event_service.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/file_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/git_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/hooks_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/hooks_service.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/init_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/llm_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/logging_config.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/mcp_oauth_store.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/mcp_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/middleware.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/models.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/openai/__init__.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/openai/models.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/openai/router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/openai/service.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/openapi.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/persistence/__init__.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/persistence/models.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/persistence/store.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/plugins_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/plugins_service.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/profiles_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/pub_sub.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/py.typed +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/server_details_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/settings_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/skills_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/sockets.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/sub_agents_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/tool_preload_service.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/tool_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/utils.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/vscode_extensions/openhands-settings/extension.js +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/vscode_extensions/openhands-settings/package.json +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/vscode_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/vscode_service.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/workspace_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/workspaces_router.py +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands_agent_server.egg-info/SOURCES.txt +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands_agent_server.egg-info/dependency_links.txt +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands_agent_server.egg-info/entry_points.txt +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands_agent_server.egg-info/requires.txt +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands_agent_server.egg-info/top_level.txt +0 -0
- {openhands_agent_server-1.32.0 → openhands_agent_server-1.34.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.34.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
|
|
@@ -19,21 +19,15 @@ from typing import Annotated, Any
|
|
|
19
19
|
from fastapi import APIRouter, HTTPException, Path, Request, status
|
|
20
20
|
from pydantic import BaseModel, Field, ValidationError
|
|
21
21
|
|
|
22
|
-
from openhands.agent_server._secrets_exposure import
|
|
23
|
-
build_expose_context,
|
|
24
|
-
get_cipher,
|
|
25
|
-
get_config,
|
|
26
|
-
parse_expose_secrets_header,
|
|
27
|
-
translate_missing_cipher,
|
|
28
|
-
)
|
|
22
|
+
from openhands.agent_server._secrets_exposure import get_cipher, get_config
|
|
29
23
|
from openhands.agent_server.persistence import (
|
|
30
24
|
PersistedSettings,
|
|
31
25
|
get_agent_profile_store,
|
|
32
26
|
get_llm_profile_store,
|
|
33
27
|
get_settings_store,
|
|
34
28
|
)
|
|
35
|
-
from openhands.agent_server.profiles_router import MAX_PROFILES
|
|
36
|
-
from openhands.agent_server.skills_service import
|
|
29
|
+
from openhands.agent_server.profiles_router import MAX_PROFILES, _has_api_key
|
|
30
|
+
from openhands.agent_server.skills_service import discover_profile_skills
|
|
37
31
|
from openhands.sdk.llm import LLM
|
|
38
32
|
from openhands.sdk.llm.llm_profile_store import (
|
|
39
33
|
ProfileLimitExceeded as LLMProfileLimitExceeded,
|
|
@@ -130,6 +124,25 @@ def _store_errors() -> Iterator[None]:
|
|
|
130
124
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
|
131
125
|
|
|
132
126
|
|
|
127
|
+
def _llm_has_real_config(llm: LLM) -> bool:
|
|
128
|
+
"""True when ``llm`` carries real, user-provided configuration.
|
|
129
|
+
|
|
130
|
+
Distinguishes an already-working setup — a real API key, subscription auth,
|
|
131
|
+
or a custom endpoint — from bare SDK field defaults on a never-configured
|
|
132
|
+
account (``model="gpt-5.5"``, no ``api_key``). Only the former is worth
|
|
133
|
+
mirroring into a named ``default`` LLM profile; persisting the latter leaves
|
|
134
|
+
a keyless, non-functional "ghost" profile that the user never asked for and
|
|
135
|
+
that nothing ever cleans up (#4031).
|
|
136
|
+
"""
|
|
137
|
+
if _has_api_key(llm):
|
|
138
|
+
return True
|
|
139
|
+
if llm.is_subscription or llm.auth_type == "subscription":
|
|
140
|
+
return True
|
|
141
|
+
if llm.base_url and llm.base_url.strip():
|
|
142
|
+
return True
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
|
|
133
146
|
def _seed_default_llm_profile(llm: LLM, cipher: Cipher | None) -> str:
|
|
134
147
|
"""Mirror the live LLM config into a ``SEED_PROFILE_NAME`` LLM profile.
|
|
135
148
|
|
|
@@ -141,6 +154,17 @@ def _seed_default_llm_profile(llm: LLM, cipher: Cipher | None) -> str:
|
|
|
141
154
|
``agent_settings.llm`` under that name so the reference resolves, unless a
|
|
142
155
|
profile is already stored there (never clobber it).
|
|
143
156
|
|
|
157
|
+
Only a *real, pre-existing* LLM config is persisted (``_llm_has_real_config``
|
|
158
|
+
— an API key, subscription auth, or a custom base_url), which is exactly the
|
|
159
|
+
legacy/cloud migration case the backfill exists for. On a fresh,
|
|
160
|
+
never-configured account the live LLM is untouched SDK defaults (keyless
|
|
161
|
+
``gpt-5.5``); persisting *that* only litters the profile list with a keyless,
|
|
162
|
+
unusable ``default`` "ghost" profile (#4031), so we skip the save and leave
|
|
163
|
+
``llm_profile_ref="default"`` as a soft/dangling ref — the canvas
|
|
164
|
+
"LLM not configured" banner and the legible launch-time error already cover
|
|
165
|
+
that state, and materialize/dry-run reports it as ``dangling_llm_profile_ref``
|
|
166
|
+
rather than raising.
|
|
167
|
+
|
|
144
168
|
Existence is checked via ``load()``, not ``list()``: the store resolves a
|
|
145
169
|
name straight to a filesystem path, so on a case-insensitive filesystem
|
|
146
170
|
(macOS/Windows) a differently-cased ``Default`` profile already occupies
|
|
@@ -164,6 +188,15 @@ def _seed_default_llm_profile(llm: LLM, cipher: Cipher | None) -> str:
|
|
|
164
188
|
"failed to load; leaving it as-is"
|
|
165
189
|
)
|
|
166
190
|
return SEED_PROFILE_NAME
|
|
191
|
+
if not _llm_has_real_config(llm):
|
|
192
|
+
# Never-configured account: don't persist a keyless ghost profile.
|
|
193
|
+
# Leave the ref soft/dangling; the banner + launch-time error handle
|
|
194
|
+
# it, and it resolves for real once the user saves a profile.
|
|
195
|
+
logger.info(
|
|
196
|
+
f"Skipping default LLM profile '{SEED_PROFILE_NAME}' seed: "
|
|
197
|
+
"no real LLM config yet (leaving llm_profile_ref as a soft ref)"
|
|
198
|
+
)
|
|
199
|
+
return SEED_PROFILE_NAME
|
|
167
200
|
try:
|
|
168
201
|
llm_store.save(
|
|
169
202
|
SEED_PROFILE_NAME,
|
|
@@ -213,7 +246,7 @@ def _seed_default_profile(
|
|
|
213
246
|
settings.agent_settings.llm, cipher
|
|
214
247
|
)
|
|
215
248
|
profile = build_seed_profile(settings.agent_settings, active_llm_profile)
|
|
216
|
-
store.save(profile,
|
|
249
|
+
store.save(profile, max_profiles=MAX_AGENT_PROFILES)
|
|
217
250
|
|
|
218
251
|
profile_id = str(profile.id)
|
|
219
252
|
settings_store = get_settings_store(get_config(request))
|
|
@@ -266,31 +299,24 @@ async def list_agent_profiles(request: Request) -> AgentProfileListResponse:
|
|
|
266
299
|
|
|
267
300
|
|
|
268
301
|
@agent_profiles_router.get("/{name}", response_model=AgentProfileDetailResponse)
|
|
269
|
-
async def get_agent_profile(
|
|
270
|
-
request: Request, name: ProfileName
|
|
271
|
-
) -> AgentProfileDetailResponse:
|
|
302
|
+
async def get_agent_profile(name: ProfileName) -> AgentProfileDetailResponse:
|
|
272
303
|
"""Get a stored profile.
|
|
273
304
|
|
|
274
|
-
|
|
275
|
-
|
|
305
|
+
A profile is secret-free at rest (#4017), so there is nothing to mask or
|
|
306
|
+
expose — unlike the LLM ``/api/profiles`` router, this endpoint has no
|
|
307
|
+
``X-Expose-Secrets`` behavior.
|
|
276
308
|
"""
|
|
277
|
-
expose_mode = parse_expose_secrets_header(request)
|
|
278
|
-
cipher = get_cipher(request)
|
|
279
|
-
|
|
280
309
|
store = get_agent_profile_store()
|
|
281
310
|
try:
|
|
282
311
|
with _store_errors():
|
|
283
|
-
profile = store.load(name
|
|
312
|
+
profile = store.load(name)
|
|
284
313
|
except FileNotFoundError:
|
|
285
314
|
raise HTTPException(
|
|
286
315
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
287
316
|
detail=f"Agent profile '{name}' not found",
|
|
288
317
|
)
|
|
289
318
|
|
|
290
|
-
|
|
291
|
-
with translate_missing_cipher():
|
|
292
|
-
payload = profile.model_dump(mode="json", context=context)
|
|
293
|
-
|
|
319
|
+
payload = profile.model_dump(mode="json")
|
|
294
320
|
return AgentProfileDetailResponse(name=name, profile=payload)
|
|
295
321
|
|
|
296
322
|
|
|
@@ -300,31 +326,29 @@ async def get_agent_profile(
|
|
|
300
326
|
status_code=status.HTTP_201_CREATED,
|
|
301
327
|
)
|
|
302
328
|
async def save_agent_profile(
|
|
303
|
-
|
|
329
|
+
name: ProfileName, body: dict[str, Any]
|
|
304
330
|
) -> AgentProfileMutationResponse:
|
|
305
331
|
"""Save an ``AgentProfile`` under ``name`` (overwriting a namesake).
|
|
306
332
|
|
|
307
333
|
The path ``name`` is authoritative — it overrides any ``name`` in the body.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
334
|
+
The profile is secret-free at rest (#4017), so no cipher/encryption is
|
|
335
|
+
involved. Returns 409 if creating a new profile would exceed
|
|
336
|
+
``MAX_AGENT_PROFILES``.
|
|
311
337
|
"""
|
|
312
|
-
cipher = get_cipher(request)
|
|
313
338
|
try:
|
|
314
339
|
profile = validate_agent_profile({**body, "name": name})
|
|
315
340
|
except ValidationError as e:
|
|
316
|
-
# Match FastAPI's request-validation shape (``detail`` is a list of
|
|
317
|
-
# objects)
|
|
318
|
-
#
|
|
319
|
-
# ``msg``.
|
|
341
|
+
# Match FastAPI's request-validation shape (``detail`` is a list of
|
|
342
|
+
# error objects): ``loc``/``type``/``msg`` (``input`` dropped — see
|
|
343
|
+
# safe_validation_error_detail's docstring for why msg is now safe).
|
|
320
344
|
raise HTTPException(
|
|
321
345
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
322
346
|
detail=safe_validation_error_detail(e),
|
|
323
347
|
)
|
|
324
348
|
except Exception:
|
|
325
|
-
# Any other validation failure (e.g.
|
|
326
|
-
#
|
|
327
|
-
#
|
|
349
|
+
# Any other validation failure (e.g. a schema/migration error) is a
|
|
350
|
+
# client error, never a 500. Stay generic — these messages can embed
|
|
351
|
+
# the input.
|
|
328
352
|
raise HTTPException(
|
|
329
353
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
330
354
|
detail="Invalid agent profile",
|
|
@@ -339,7 +363,7 @@ async def save_agent_profile(
|
|
|
339
363
|
try:
|
|
340
364
|
with _store_errors():
|
|
341
365
|
save_profile_preserving_identity(
|
|
342
|
-
store, profile,
|
|
366
|
+
store, profile, max_profiles=MAX_AGENT_PROFILES
|
|
343
367
|
)
|
|
344
368
|
except ProfileLimitExceeded:
|
|
345
369
|
raise HTTPException(
|
|
@@ -489,36 +513,37 @@ async def materialize_agent_profile(
|
|
|
489
513
|
than raising — the only error status is 404 (unknown profile name).
|
|
490
514
|
resolved_settings is redacted (api_key_set booleans; no raw secrets).
|
|
491
515
|
"""
|
|
492
|
-
cipher = get_cipher(request)
|
|
493
|
-
|
|
494
516
|
store = get_agent_profile_store()
|
|
495
517
|
try:
|
|
496
518
|
with _store_errors():
|
|
497
|
-
profile = store.load(name
|
|
519
|
+
profile = store.load(name)
|
|
498
520
|
except FileNotFoundError:
|
|
499
521
|
raise HTTPException(
|
|
500
522
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
501
523
|
detail=f"Agent profile '{name}' not found",
|
|
502
524
|
)
|
|
503
525
|
|
|
526
|
+
# Still needed here (unlike the profile load above): resolve_agent_profile_
|
|
527
|
+
# dry_run uses it to decrypt the *referenced LLM profile's* own secret.
|
|
528
|
+
cipher = get_cipher(request)
|
|
504
529
|
config = get_config(request)
|
|
505
530
|
settings = get_settings_store(config).load() or PersistedSettings()
|
|
506
531
|
mcp_config = settings.agent_settings.mcp_config
|
|
507
532
|
|
|
508
|
-
# Discover skills off the event loop so the dry-run can report which
|
|
509
|
-
# ``
|
|
510
|
-
#
|
|
511
|
-
#
|
|
512
|
-
#
|
|
533
|
+
# Discover skills off the event loop so the dry-run can report which skills
|
|
534
|
+
# (catalog minus ``disabled_skills``) resolve. Only OpenHands profiles carry
|
|
535
|
+
# user/public skills; ACP profiles do not. A discovery failure must not 500
|
|
536
|
+
# the preview: pass ``available_skills=None`` and surface the failure as its
|
|
537
|
+
# own diagnostic below.
|
|
513
538
|
discovery_error: str | None = None
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
539
|
+
available_skills = None
|
|
540
|
+
if profile.agent_kind == "openhands":
|
|
541
|
+
try:
|
|
542
|
+
available_skills = await asyncio.to_thread(discover_profile_skills)
|
|
543
|
+
except Exception as exc:
|
|
544
|
+
available_skills = None
|
|
545
|
+
discovery_error = str(exc)
|
|
546
|
+
logger.warning("Skill discovery failed during materialize: %s", exc)
|
|
522
547
|
|
|
523
548
|
llm_store = get_llm_profile_store()
|
|
524
549
|
diagnostics = resolve_agent_profile_dry_run(
|
|
@@ -51,7 +51,6 @@ from openhands.sdk.marketplace.registry import (
|
|
|
51
51
|
from openhands.sdk.plugin import PluginFetchError
|
|
52
52
|
from openhands.sdk.profiles.resolver import (
|
|
53
53
|
DanglingMcpServerRef,
|
|
54
|
-
DanglingSkillRef,
|
|
55
54
|
ProfileNotFound,
|
|
56
55
|
)
|
|
57
56
|
from openhands.sdk.tool.client_tool import ClientToolRegistrationError
|
|
@@ -217,11 +216,6 @@ async def start_conversation(
|
|
|
217
216
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
218
217
|
detail={"message": str(e), "dangling_mcp_server_refs": e.missing},
|
|
219
218
|
) from e
|
|
220
|
-
except DanglingSkillRef as e:
|
|
221
|
-
raise HTTPException(
|
|
222
|
-
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
|
223
|
-
detail={"message": str(e), "dangling_skill_refs": e.missing},
|
|
224
|
-
) from e
|
|
225
219
|
except ClientToolRegistrationError as e:
|
|
226
220
|
raise HTTPException(
|
|
227
221
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e)
|
|
@@ -32,7 +32,7 @@ from openhands.agent_server.models import (
|
|
|
32
32
|
)
|
|
33
33
|
from openhands.agent_server.pub_sub import Subscriber
|
|
34
34
|
from openhands.agent_server.server_details_router import update_last_execution_time
|
|
35
|
-
from openhands.agent_server.skills_service import
|
|
35
|
+
from openhands.agent_server.skills_service import discover_profile_skills
|
|
36
36
|
from openhands.agent_server.utils import safe_rmtree, utc_now
|
|
37
37
|
from openhands.sdk import LLM, AgentContext, Event, Message
|
|
38
38
|
from openhands.sdk.agent.base import AgentBase
|
|
@@ -267,7 +267,6 @@ def _resolve_agent_from_profile(
|
|
|
267
267
|
Raises:
|
|
268
268
|
ProfileNotFound: No stored profile has ``profile_id``.
|
|
269
269
|
DanglingMcpServerRef: A referenced MCP server is absent from the global config.
|
|
270
|
-
DanglingSkillRef: A referenced skill is absent from the discovered catalog.
|
|
271
270
|
ValueError: Profile load or settings validation failure.
|
|
272
271
|
"""
|
|
273
272
|
from openhands.agent_server.persistence.store import (
|
|
@@ -275,6 +274,7 @@ def _resolve_agent_from_profile(
|
|
|
275
274
|
get_llm_profile_store,
|
|
276
275
|
)
|
|
277
276
|
from openhands.sdk.profiles.resolver import ProfileNotFound, resolve_agent_profile
|
|
277
|
+
from openhands.sdk.settings.model import OpenHandsAgentSettings
|
|
278
278
|
|
|
279
279
|
store = get_agent_profile_store()
|
|
280
280
|
profile_name = store.name_for_id(profile_id)
|
|
@@ -282,7 +282,7 @@ def _resolve_agent_from_profile(
|
|
|
282
282
|
raise ProfileNotFound(f"Agent profile with id '{profile_id}' not found")
|
|
283
283
|
|
|
284
284
|
try:
|
|
285
|
-
profile = store.load(profile_name
|
|
285
|
+
profile = store.load(profile_name)
|
|
286
286
|
except FileNotFoundError:
|
|
287
287
|
raise ProfileNotFound(
|
|
288
288
|
f"Agent profile '{profile_name}' (id={profile_id}) not found"
|
|
@@ -292,15 +292,18 @@ def _resolve_agent_from_profile(
|
|
|
292
292
|
f"Failed to load agent profile '{profile_name}': {exc}"
|
|
293
293
|
) from exc
|
|
294
294
|
|
|
295
|
-
#
|
|
296
|
-
#
|
|
297
|
-
#
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
295
|
+
# OpenHands profiles get the discovered catalog minus their ``disabled_skills``
|
|
296
|
+
# deny-list; ACP profiles carry no user/public skills so discovery is skipped.
|
|
297
|
+
# A genuine discovery failure fails the launch loudly rather than silently
|
|
298
|
+
# producing a zero-skill agent.
|
|
299
|
+
available_skills = None
|
|
300
|
+
if profile.agent_kind == "openhands":
|
|
301
|
+
try:
|
|
302
|
+
available_skills = discover_profile_skills()
|
|
303
|
+
except Exception as exc:
|
|
304
|
+
raise ValueError(
|
|
305
|
+
f"Skill discovery failed for profile '{profile_name}': {exc}"
|
|
306
|
+
) from exc
|
|
304
307
|
|
|
305
308
|
llm_store = get_llm_profile_store()
|
|
306
309
|
try:
|
|
@@ -314,6 +317,16 @@ def _resolve_agent_from_profile(
|
|
|
314
317
|
except (TypeError, ValueError) as exc:
|
|
315
318
|
raise ValueError(f"Profile '{profile_name}' failed to resolve: {exc}") from exc
|
|
316
319
|
|
|
320
|
+
if isinstance(settings_config, OpenHandsAgentSettings):
|
|
321
|
+
# Force streaming so this launch path wires on_token: a client can't set
|
|
322
|
+
# llm.stream on a profile's referenced LLM ahead of time. Safe at this
|
|
323
|
+
# layer (not the SDK resolver) because this server wires the token
|
|
324
|
+
# callback whenever any llm.stream is set; a headless resolver caller
|
|
325
|
+
# that never wires on_token is covered by LLM's graceful degradation.
|
|
326
|
+
settings_config = settings_config.model_copy(
|
|
327
|
+
update={"llm": settings_config.llm.model_copy(update={"stream": True})}
|
|
328
|
+
)
|
|
329
|
+
|
|
317
330
|
agent = settings_config.create_agent()
|
|
318
331
|
# Browser is deliberately absent from the deterministic SDK default
|
|
319
332
|
# (environment-dependent); this server knows its runtime, so it injects
|
|
@@ -21,7 +21,6 @@ import tempfile
|
|
|
21
21
|
from dataclasses import dataclass
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
from time import monotonic
|
|
24
|
-
from typing import TYPE_CHECKING
|
|
25
24
|
|
|
26
25
|
from pydantic import BaseModel, ValidationError
|
|
27
26
|
|
|
@@ -57,10 +56,6 @@ from openhands.sdk.utils import sanitized_env
|
|
|
57
56
|
from openhands.sdk.utils.path import to_posix_path
|
|
58
57
|
|
|
59
58
|
|
|
60
|
-
if TYPE_CHECKING:
|
|
61
|
-
from openhands.sdk.profiles import ACPAgentProfile, OpenHandsAgentProfile
|
|
62
|
-
|
|
63
|
-
|
|
64
59
|
logger = get_logger(__name__)
|
|
65
60
|
|
|
66
61
|
|
|
@@ -458,20 +453,21 @@ def load_all_skills(
|
|
|
458
453
|
|
|
459
454
|
|
|
460
455
|
def discover_profile_skills() -> list[Skill]:
|
|
461
|
-
"""Skill catalog
|
|
456
|
+
"""Skill catalog an OpenHands profile launches with (#4017).
|
|
462
457
|
|
|
463
458
|
Returns the merged user + public skills — the deterministic sources of
|
|
464
|
-
:func:`load_all_skills
|
|
465
|
-
``
|
|
466
|
-
this does not swallow errors:
|
|
467
|
-
silently resolving the profile
|
|
459
|
+
:func:`load_all_skills`. ``resolve_agent_profile`` keeps all of them except
|
|
460
|
+
the profile's ``disabled_skills`` deny-list. ``load_all_skills`` already
|
|
461
|
+
absorbs and logs benign per-source failures, so this does not swallow errors:
|
|
462
|
+
an unexpected failure propagates rather than silently resolving the profile
|
|
463
|
+
to a zero-skill agent.
|
|
468
464
|
|
|
469
465
|
Org / project skills need auth / workspace context not available at resolve
|
|
470
|
-
time, so they are not in this catalog
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
466
|
+
time, so they are not in this catalog. That is safe under the deny-list
|
|
467
|
+
model: a skill missing from the catalog is simply never disabled (it stays
|
|
468
|
+
on when it does load, e.g. project skills loaded lazily by
|
|
469
|
+
``LocalConversation``) — the old allow-list would instead have dangled and
|
|
470
|
+
failed the launch.
|
|
475
471
|
"""
|
|
476
472
|
return list(
|
|
477
473
|
load_all_skills(
|
|
@@ -483,21 +479,6 @@ def discover_profile_skills() -> list[Skill]:
|
|
|
483
479
|
)
|
|
484
480
|
|
|
485
481
|
|
|
486
|
-
def discover_profile_skills_if_needed(
|
|
487
|
-
profile: "OpenHandsAgentProfile | ACPAgentProfile",
|
|
488
|
-
) -> list[Skill] | None:
|
|
489
|
-
"""Discover the skill catalog a profile needs, or ``None`` to skip discovery.
|
|
490
|
-
|
|
491
|
-
``skill_refs == []`` selects no discovered skills, so the (potentially
|
|
492
|
-
network-bound) discovery is skipped and the resolver receives ``None``. Any
|
|
493
|
-
other value — ``None`` (all discovered) or a name list — needs the catalog.
|
|
494
|
-
Centralizes the skip guard shared by conversation start and the dry-run.
|
|
495
|
-
"""
|
|
496
|
-
if profile.skill_refs == []:
|
|
497
|
-
return None
|
|
498
|
-
return discover_profile_skills()
|
|
499
|
-
|
|
500
|
-
|
|
501
482
|
def sync_public_skills() -> tuple[bool, str]:
|
|
502
483
|
"""Force refresh of public skills from GitHub repository.
|
|
503
484
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openhands-agent-server
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.34.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
|
{openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/__init__.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/api.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.32.0 → openhands_agent_server-1.34.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
|
{openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/env_parser.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/git_router.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/llm_router.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/mcp_router.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/middleware.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.32.0 → openhands_agent_server-1.34.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.32.0 → openhands_agent_server-1.34.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.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/pub_sub.py
RENAMED
|
File without changes
|
{openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.32.0 → openhands_agent_server-1.34.0}/openhands/agent_server/sockets.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openhands_agent_server-1.32.0 → openhands_agent_server-1.34.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
|