openhands-agent-server 1.45.0__tar.gz → 1.47.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.45.0 → openhands_agent_server-1.47.0}/PKG-INFO +1 -1
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/canvas_extensions_router.py +24 -5
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/event_service.py +42 -0
- openhands_agent_server-1.47.0/openhands/agent_server/openai/models.py +133 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/openai/router.py +37 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/openai/service.py +327 -58
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/profiles_router.py +17 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/session_protocol.py +10 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/session_socket.py +64 -7
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands_agent_server.egg-info/PKG-INFO +1 -1
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/pyproject.toml +1 -1
- openhands_agent_server-1.45.0/openhands/agent_server/openai/models.py +0 -59
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/__init__.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/__main__.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/_secret_redaction.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/_secrets_exposure.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/agent_profiles_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/api.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/auth_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/bash_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/bash_service.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/canvas_extensions/__init__.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/canvas_extensions/installed.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/canvas_extensions/manifest.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/config.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/conversation_lease.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/conversation_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/conversation_service.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/credential_binding.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/dependencies.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/desktop_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/docker/Dockerfile +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/docker/build.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/env_parser.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/event_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/file_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/git_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/hooks_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/hooks_service.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/init_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/llm_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/logging_config.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/mcp_oauth_store.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/mcp_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/middleware.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/models.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/openai/__init__.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/openapi.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/persistence/__init__.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/persistence/models.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/persistence/store.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/plugins_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/plugins_service.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/provider_connections_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/pub_sub.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/py.typed +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/server_details_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/settings_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/skills_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/skills_service.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/sockets.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/sub_agents_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/telemetry/__init__.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/telemetry/factory.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/telemetry/http_exporter.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/telemetry/models.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/telemetry/policy.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/telemetry/posthog_exporter.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/telemetry/sanitizer.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/telemetry/service.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/telemetry/sink.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/telemetry/subscriber.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/telemetry_types.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/tool_preload_service.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/tool_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/utils.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/vscode_extensions/openhands-settings/extension.js +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/vscode_extensions/openhands-settings/package.json +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/vscode_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/vscode_service.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/workspace_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands/agent_server/workspaces_router.py +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands_agent_server.egg-info/SOURCES.txt +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands_agent_server.egg-info/dependency_links.txt +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands_agent_server.egg-info/entry_points.txt +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands_agent_server.egg-info/requires.txt +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.0}/openhands_agent_server.egg-info/top_level.txt +0 -0
- {openhands_agent_server-1.45.0 → openhands_agent_server-1.47.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.47.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
|
|
@@ -24,8 +24,12 @@ from openhands.agent_server.canvas_extensions.installed import (
|
|
|
24
24
|
list_installed_canvas_extensions,
|
|
25
25
|
uninstall_canvas_extension,
|
|
26
26
|
)
|
|
27
|
-
from openhands.agent_server.canvas_extensions.manifest import
|
|
27
|
+
from openhands.agent_server.canvas_extensions.manifest import (
|
|
28
|
+
MANIFEST_FILENAME,
|
|
29
|
+
CanvasExtensionManifest,
|
|
30
|
+
)
|
|
28
31
|
from openhands.sdk.extensions.fetch import ExtensionFetchError
|
|
32
|
+
from openhands.sdk.utils.redact import redact_url_credentials
|
|
29
33
|
|
|
30
34
|
|
|
31
35
|
canvas_extensions_router = APIRouter(
|
|
@@ -144,7 +148,7 @@ class UninstallCanvasExtensionResponse(BaseModel):
|
|
|
144
148
|
"/install",
|
|
145
149
|
response_model=InstalledCanvasExtensionResponse,
|
|
146
150
|
responses={
|
|
147
|
-
400: {"description": "
|
|
151
|
+
400: {"description": "Could not read canvas extension source"},
|
|
148
152
|
409: {"description": "Canvas extension already installed (use force=true)"},
|
|
149
153
|
422: {"description": "Invalid canvas extension (bad manifest, etc.)"},
|
|
150
154
|
},
|
|
@@ -173,12 +177,27 @@ def install_canvas_extension_endpoint(
|
|
|
173
177
|
status_code=409,
|
|
174
178
|
detail="Canvas extension already installed. Use force=true to overwrite.",
|
|
175
179
|
)
|
|
176
|
-
except ExtensionFetchError:
|
|
180
|
+
except ExtensionFetchError as e:
|
|
181
|
+
# Pass the specific reason through -- which of source/ref/repo_path is
|
|
182
|
+
# wrong is the whole diagnostic, and a generic message misattributes a
|
|
183
|
+
# bad repo_path to the source. Redacted in case a URL carried a token.
|
|
184
|
+
# Deliberately not worded "failed to fetch": the canvas client treats
|
|
185
|
+
# that phrase as a network outage and hides the reason behind a
|
|
186
|
+
# "Disconnected, check your network" toast.
|
|
177
187
|
raise HTTPException(
|
|
178
188
|
status_code=400,
|
|
179
189
|
detail=(
|
|
180
|
-
"
|
|
181
|
-
"
|
|
190
|
+
"Could not read canvas extension source: "
|
|
191
|
+
f"{redact_url_credentials(str(e))}"
|
|
192
|
+
),
|
|
193
|
+
)
|
|
194
|
+
except FileNotFoundError:
|
|
195
|
+
raise HTTPException(
|
|
196
|
+
status_code=422,
|
|
197
|
+
detail=(
|
|
198
|
+
f"No {MANIFEST_FILENAME} found at the resolved location. "
|
|
199
|
+
"Source plus Path must point at the extension's own "
|
|
200
|
+
"directory, not the repository root."
|
|
182
201
|
),
|
|
183
202
|
)
|
|
184
203
|
except (ValidationError, ValueError, OSError):
|
|
@@ -31,6 +31,7 @@ from openhands.sdk.agent.acp_file_credentials import (
|
|
|
31
31
|
CODEX_AUTH_SECRET_NAME,
|
|
32
32
|
is_valid_codex_auth,
|
|
33
33
|
)
|
|
34
|
+
from openhands.sdk.agent.stream_context import StreamProgress
|
|
34
35
|
from openhands.sdk.conversation.base import BaseConversation
|
|
35
36
|
from openhands.sdk.conversation.events_list_base import EventsListBase
|
|
36
37
|
from openhands.sdk.conversation.exceptions import ConversationRunError
|
|
@@ -135,6 +136,11 @@ class EventService:
|
|
|
135
136
|
_pub_sub: PubSub[Event] = field(
|
|
136
137
|
default_factory=lambda: PubSub[Event](max_subscribers=50), init=False
|
|
137
138
|
)
|
|
139
|
+
# Its own fan-out, not the event bus: frames are not events, and only the
|
|
140
|
+
# session socket consumes them.
|
|
141
|
+
_stream_pub_sub: PubSub[StreamProgress] = field(
|
|
142
|
+
default_factory=lambda: PubSub[StreamProgress](max_subscribers=50), init=False
|
|
143
|
+
)
|
|
138
144
|
_run_task: asyncio.Task | None = field(default=None, init=False)
|
|
139
145
|
# Set when a send_message(run=True) is rejected because a run is still
|
|
140
146
|
# wrapping up; consumed by _run_and_publish to re-run the stranded message.
|
|
@@ -848,6 +854,19 @@ class EventService:
|
|
|
848
854
|
async def unsubscribe_from_events(self, subscriber_id: UUID) -> bool:
|
|
849
855
|
return self._pub_sub.unsubscribe(subscriber_id)
|
|
850
856
|
|
|
857
|
+
async def subscribe_to_stream_progress(
|
|
858
|
+
self, subscriber: Subscriber[StreamProgress]
|
|
859
|
+
) -> UUID:
|
|
860
|
+
"""Register for stream-progress frames.
|
|
861
|
+
|
|
862
|
+
No initial push, unlike :meth:`subscribe_to_events`: a client that
|
|
863
|
+
connects mid-stream gets the real text with the durable event.
|
|
864
|
+
"""
|
|
865
|
+
return self._stream_pub_sub.subscribe(subscriber)
|
|
866
|
+
|
|
867
|
+
async def unsubscribe_from_stream_progress(self, subscriber_id: UUID) -> bool:
|
|
868
|
+
return self._stream_pub_sub.unsubscribe(subscriber_id)
|
|
869
|
+
|
|
851
870
|
def _emit_event_from_thread(self, event: Event) -> None:
|
|
852
871
|
"""Helper to safely emit events from non-async contexts (e.g., callbacks).
|
|
853
872
|
|
|
@@ -1075,6 +1094,16 @@ class EventService:
|
|
|
1075
1094
|
with suppress(RuntimeError): # main loop already closed during teardown
|
|
1076
1095
|
asyncio.run_coroutine_threadsafe(self._pub_sub(event), self._main_loop)
|
|
1077
1096
|
|
|
1097
|
+
def _publish_stream_progress(frame: StreamProgress) -> None:
|
|
1098
|
+
# Same cross-thread hop as _publish_stream_delta: called from the
|
|
1099
|
+
# run thread, or the ACP portal thread.
|
|
1100
|
+
if not self._main_loop or not self._main_loop.is_running():
|
|
1101
|
+
return
|
|
1102
|
+
with suppress(RuntimeError): # main loop already closed during teardown
|
|
1103
|
+
asyncio.run_coroutine_threadsafe(
|
|
1104
|
+
self._stream_pub_sub(frame), self._main_loop
|
|
1105
|
+
)
|
|
1106
|
+
|
|
1078
1107
|
def _token_streaming_callback(chunk: LLMStreamChunk | str) -> None:
|
|
1079
1108
|
if isinstance(chunk, str):
|
|
1080
1109
|
_publish_stream_delta(content=chunk)
|
|
@@ -1099,6 +1128,7 @@ class EventService:
|
|
|
1099
1128
|
conversation_id=self.stored.id,
|
|
1100
1129
|
callbacks=[self._callback_wrapper],
|
|
1101
1130
|
token_callbacks=([_token_streaming_callback] if streaming_enabled else []),
|
|
1131
|
+
stream_callbacks=[_publish_stream_progress],
|
|
1102
1132
|
max_iteration_per_run=self.stored.max_iterations,
|
|
1103
1133
|
stuck_detection=self.stored.stuck_detection,
|
|
1104
1134
|
visualizer=None,
|
|
@@ -1357,6 +1387,17 @@ class EventService:
|
|
|
1357
1387
|
# Create task but don't await it - runs in background
|
|
1358
1388
|
self._run_task = asyncio.create_task(_run_and_publish())
|
|
1359
1389
|
|
|
1390
|
+
async def wait_for_run_completion(
|
|
1391
|
+
self, timeout: float | None = None
|
|
1392
|
+
) -> ConversationExecutionStatus:
|
|
1393
|
+
"""Wait for the active run task without cancelling it on timeout."""
|
|
1394
|
+
run_task = self._run_task
|
|
1395
|
+
if run_task is not None:
|
|
1396
|
+
done, _ = await asyncio.wait({run_task}, timeout=timeout)
|
|
1397
|
+
if not done:
|
|
1398
|
+
raise TimeoutError("Conversation run timed out")
|
|
1399
|
+
return await self._get_execution_status()
|
|
1400
|
+
|
|
1360
1401
|
async def start_goal_loop(
|
|
1361
1402
|
self,
|
|
1362
1403
|
objective: str,
|
|
@@ -1774,6 +1815,7 @@ class EventService:
|
|
|
1774
1815
|
self._run_task = None
|
|
1775
1816
|
|
|
1776
1817
|
await self._pub_sub.close()
|
|
1818
|
+
await self._stream_pub_sub.close()
|
|
1777
1819
|
if self._conversation:
|
|
1778
1820
|
loop = asyncio.get_running_loop()
|
|
1779
1821
|
await loop.run_in_executor(None, self._conversation.close)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Models for the OpenAI-compatible agent-server gateway."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from openai.types import CompletionUsage, Model
|
|
6
|
+
from openai.types.chat import ChatCompletion, ChatCompletionChunk
|
|
7
|
+
from openai.types.chat.chat_completion import Choice
|
|
8
|
+
from openai.types.chat.chat_completion_chunk import Choice as ChunkChoice, ChoiceDelta
|
|
9
|
+
from openai.types.chat.chat_completion_message import ChatCompletionMessage
|
|
10
|
+
from pydantic import BaseModel, ConfigDict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
OpenAIChatCompletionChoice = Choice
|
|
14
|
+
OpenAIChatCompletionChunk = ChatCompletionChunk
|
|
15
|
+
OpenAIChatCompletionChunkChoice = ChunkChoice
|
|
16
|
+
OpenAIChatCompletionChunkChoiceDelta = ChoiceDelta
|
|
17
|
+
OpenAIChatCompletionResponse = ChatCompletion
|
|
18
|
+
OpenAIModel = Model
|
|
19
|
+
OpenAIResponseMessage = ChatCompletionMessage
|
|
20
|
+
OpenAIUsage = CompletionUsage
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class OpenAIResponseInputTokensDetails(BaseModel):
|
|
24
|
+
cached_tokens: int
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class OpenAIResponseOutputTokensDetails(BaseModel):
|
|
28
|
+
reasoning_tokens: int
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class OpenAIResponseUsage(BaseModel):
|
|
32
|
+
input_tokens: int
|
|
33
|
+
input_tokens_details: OpenAIResponseInputTokensDetails
|
|
34
|
+
output_tokens: int
|
|
35
|
+
output_tokens_details: OpenAIResponseOutputTokensDetails
|
|
36
|
+
total_tokens: int
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class OpenAIResponseOutputText(BaseModel):
|
|
40
|
+
annotations: list[dict[str, object]]
|
|
41
|
+
text: str
|
|
42
|
+
type: Literal["output_text"] = "output_text"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class OpenAIResponseOutputMessage(BaseModel):
|
|
46
|
+
id: str
|
|
47
|
+
content: list[OpenAIResponseOutputText]
|
|
48
|
+
role: Literal["assistant"] = "assistant"
|
|
49
|
+
status: Literal["completed"] = "completed"
|
|
50
|
+
type: Literal["message"] = "message"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class OpenAIResponse(BaseModel):
|
|
54
|
+
id: str
|
|
55
|
+
created_at: float
|
|
56
|
+
completed_at: float
|
|
57
|
+
instructions: str | None = None
|
|
58
|
+
metadata: dict[str, str] | None = None
|
|
59
|
+
model: str
|
|
60
|
+
object: Literal["response"] = "response"
|
|
61
|
+
output: list[OpenAIResponseOutputMessage]
|
|
62
|
+
parallel_tool_calls: bool = False
|
|
63
|
+
previous_response_id: str | None = None
|
|
64
|
+
status: Literal["completed"] = "completed"
|
|
65
|
+
tool_choice: Literal["none"] = "none"
|
|
66
|
+
tools: list[dict[str, str]]
|
|
67
|
+
usage: OpenAIResponseUsage
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class OpenAIImageURL(BaseModel):
|
|
71
|
+
url: str
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class OpenAIContentPart(BaseModel):
|
|
75
|
+
type: str
|
|
76
|
+
text: str | None = None
|
|
77
|
+
image_url: OpenAIImageURL | str | None = None
|
|
78
|
+
|
|
79
|
+
model_config = ConfigDict(extra="ignore")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class OpenAIChatMessage(BaseModel):
|
|
83
|
+
role: Literal["system", "developer", "user", "assistant", "tool"]
|
|
84
|
+
content: str | list[OpenAIContentPart] | None = None
|
|
85
|
+
|
|
86
|
+
model_config = ConfigDict(extra="ignore")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class OpenAIStreamOptions(BaseModel):
|
|
90
|
+
include_usage: bool = False
|
|
91
|
+
|
|
92
|
+
model_config = ConfigDict(extra="ignore")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class OpenAIChatCompletionRequest(BaseModel):
|
|
96
|
+
model: str
|
|
97
|
+
messages: list[OpenAIChatMessage]
|
|
98
|
+
stream: bool = False
|
|
99
|
+
stream_options: OpenAIStreamOptions | None = None
|
|
100
|
+
|
|
101
|
+
model_config = ConfigDict(extra="ignore")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class OpenAIResponseInputContentPart(BaseModel):
|
|
105
|
+
type: str
|
|
106
|
+
text: str | None = None
|
|
107
|
+
image_url: str | None = None
|
|
108
|
+
|
|
109
|
+
model_config = ConfigDict(extra="ignore")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class OpenAIResponseInputMessage(BaseModel):
|
|
113
|
+
role: Literal["system", "developer", "user", "assistant"]
|
|
114
|
+
content: str | list[OpenAIResponseInputContentPart]
|
|
115
|
+
|
|
116
|
+
model_config = ConfigDict(extra="ignore")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class OpenAIResponseRequest(BaseModel):
|
|
120
|
+
model: str
|
|
121
|
+
input: str | list[OpenAIResponseInputMessage]
|
|
122
|
+
instructions: str | None = None
|
|
123
|
+
previous_response_id: str | None = None
|
|
124
|
+
store: bool = False
|
|
125
|
+
stream: bool = False
|
|
126
|
+
metadata: dict[str, str] | None = None
|
|
127
|
+
|
|
128
|
+
model_config = ConfigDict(extra="ignore")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class OpenAIModelListResponse(BaseModel):
|
|
132
|
+
object: Literal["list"] = "list"
|
|
133
|
+
data: list[OpenAIModel]
|
|
@@ -16,11 +16,14 @@ from openhands.agent_server.openai.models import (
|
|
|
16
16
|
OpenAIChatCompletionRequest,
|
|
17
17
|
OpenAIChatCompletionResponse,
|
|
18
18
|
OpenAIModelListResponse,
|
|
19
|
+
OpenAIResponse,
|
|
20
|
+
OpenAIResponseRequest,
|
|
19
21
|
)
|
|
20
22
|
from openhands.agent_server.openai.service import (
|
|
21
23
|
iter_openai_chat_completion_sse,
|
|
22
24
|
list_openai_models,
|
|
23
25
|
run_chat_completion,
|
|
26
|
+
run_response,
|
|
24
27
|
)
|
|
25
28
|
from openhands.sdk.conversation.types import (
|
|
26
29
|
ConversationObservabilityMetadata,
|
|
@@ -167,3 +170,37 @@ async def create_chat_completion(
|
|
|
167
170
|
|
|
168
171
|
response.headers["X-OpenHands-ServerConversation-ID"] = conversation_id
|
|
169
172
|
return result.response
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@openai_router.post(
|
|
176
|
+
"/v1/responses",
|
|
177
|
+
response_model=OpenAIResponse,
|
|
178
|
+
response_model_exclude_none=True,
|
|
179
|
+
)
|
|
180
|
+
async def create_response(
|
|
181
|
+
body: OpenAIResponseRequest,
|
|
182
|
+
request: Request,
|
|
183
|
+
response: Response,
|
|
184
|
+
x_openhands_observability_span_name: Annotated[
|
|
185
|
+
str | None, Header(alias="X-OpenHands-Observability-Span-Name")
|
|
186
|
+
] = None,
|
|
187
|
+
x_openhands_observability_tags: Annotated[
|
|
188
|
+
str | None, Header(alias="X-OpenHands-Observability-Tags")
|
|
189
|
+
] = None,
|
|
190
|
+
x_openhands_observability_metadata: Annotated[
|
|
191
|
+
str | None, Header(alias="X-OpenHands-Observability-Metadata")
|
|
192
|
+
] = None,
|
|
193
|
+
conversation_service: ConversationService = Depends(get_conversation_service),
|
|
194
|
+
) -> OpenAIResponse:
|
|
195
|
+
result = await run_response(
|
|
196
|
+
request=body,
|
|
197
|
+
config=_get_config(request),
|
|
198
|
+
conversation_service=conversation_service,
|
|
199
|
+
observability_overrides=_parse_observability_overrides(
|
|
200
|
+
span_name=x_openhands_observability_span_name,
|
|
201
|
+
tags=x_openhands_observability_tags,
|
|
202
|
+
metadata=x_openhands_observability_metadata,
|
|
203
|
+
),
|
|
204
|
+
)
|
|
205
|
+
response.headers["X-OpenHands-ServerConversation-ID"] = str(result.conversation_id)
|
|
206
|
+
return result.response
|