Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add port mappings support #5577

Merged
merged 61 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
e41ca72
Add port mappings support to sandbox config
openhands-agent Dec 13, 2024
c7d3225
Add UI for served app
amanape Dec 13, 2024
4bc7721
Merge branch 'main' into add-port-mappings
amanape Dec 16, 2024
291c496
Create new property
amanape Dec 16, 2024
9cee2c3
Merge branch 'main' into add-port-mappings
amanape Dec 17, 2024
42876f3
Multi port support and extend base and remote runtimes
amanape Dec 17, 2024
a84f7e6
Merge and resolve
amanape Dec 17, 2024
395527d
Adjustment and changes
amanape Dec 17, 2024
e68a658
Small edit
amanape Dec 17, 2024
68215db
Add runtime-specific port mapping instructions to agent prompt
openhands-agent Dec 18, 2024
c03c1ad
Fix template modification to use file reading instead of non-existent…
openhands-agent Dec 18, 2024
468b61c
Move prompt extension logic to PromptManager class
openhands-agent Dec 18, 2024
7b4628b
Some fixes and improvements
amanape Dec 18, 2024
80d92ae
Merge and resolve
amanape Dec 19, 2024
d3018c2
Improve jinja template handling
amanape Dec 19, 2024
4b9a65a
Fix comments
amanape Dec 19, 2024
0a23fd9
Mostly renaming
amanape Dec 19, 2024
65a419f
Merge and resolve
amanape Dec 19, 2024
b1af1db
Address prompt handling
amanape Dec 19, 2024
75ca147
Merge branch 'main' into add-port-mappings
amanape Dec 20, 2024
0e700d1
Merge and resolve
amanape Dec 20, 2024
0cef034
Address comments
amanape Dec 20, 2024
eac950a
Update instructions
amanape Dec 20, 2024
301231b
Merge, resolve, and fix error
amanape Dec 23, 2024
c55fa09
Fix error
amanape Dec 23, 2024
c5b7382
merge and resolve
amanape Dec 27, 2024
a0d2efd
Rename
amanape Dec 27, 2024
30f0b49
Merge branch 'main' into add-port-mappings
tofarr Dec 27, 2024
6a3306e
Lint fix
tofarr Dec 27, 2024
7aa527d
merge
amanape Dec 31, 2024
020df3c
Merge branch 'main' into add-port-mappings
rbren Jan 1, 2025
ac973fa
make prompting clearer
rbren Jan 1, 2025
ff07644
handle errors
rbren Jan 1, 2025
0130246
fix llm err handling
rbren Jan 1, 2025
6ac55ca
Merge branch 'rb/fix-errs' into add-port-mappings
rbren Jan 1, 2025
d9814cf
json for dict configs
rbren Jan 1, 2025
a2ae2d9
fix prompt
rbren Jan 1, 2025
37c671a
dynamic port mapping
rbren Jan 1, 2025
8f67d2d
fix ports
rbren Jan 1, 2025
108bce0
fix port initialization
rbren Jan 1, 2025
6ebdf16
remove offline label
rbren Jan 1, 2025
6d078f8
fix up iframe
rbren Jan 1, 2025
3c6d5f8
change web_hosts api
rbren Jan 1, 2025
7401215
fix frontend for new api
rbren Jan 1, 2025
5122f83
delint
rbren Jan 1, 2025
aaa0b22
delint
rbren Jan 1, 2025
33cbc1e
change to work_hosts
rbren Jan 1, 2025
182071d
remove log
rbren Jan 1, 2025
6bf627d
fix vscode
rbren Jan 1, 2025
5ba5766
Merge branch 'main' into add-port-mappings
amanape Jan 2, 2025
282f5b4
Add refresh button and small active indicator
amanape Jan 2, 2025
c98ceca
Allow editing path and create redirect button
amanape Jan 2, 2025
c951c36
Merge branch 'main' into add-port-mappings
amanape Jan 2, 2025
ea7691b
Allow to edit full URL
amanape Jan 2, 2025
cfcac55
Resolve merge conflicts with main
openhands-agent Jan 3, 2025
a99fb0e
Apply pre-commit fixes
openhands-agent Jan 3, 2025
c2ab895
Merge branch 'main' into add-port-mappings
rbren Jan 6, 2025
5d51a35
change app port ranges
rbren Jan 8, 2025
99489d7
Merge branch 'main' into add-port-mappings
rbren Jan 8, 2025
1854883
Merge branch 'main' into add-port-mappings
amanape Jan 9, 2025
ae92c8f
Update openhands/runtime/impl/docker/docker_runtime.py
rbren Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions frontend/src/components/layout/served-app-label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { FaExternalLinkAlt } from "react-icons/fa";
import { useServedApp } from "#/hooks/query/use-served-app";

export function ServedAppLabel() {
const { isSuccess, isPending } = useServedApp();

return (
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-2">
App
{isPending && (
<div className="animate-pulse w-2 h-2 rounded-full bg-yellow-400" />
)}
</div>
{!isSuccess && <span className="text-red-500">Offline</span>}
{isSuccess && (
<a
href="http://localhost:4141"
target="_blank"
rel="noreferrer"
className="flex items-center gap-2"
>
<span className="text-green-500">Online</span>
<FaExternalLinkAlt fill="#a3a3a3" />
</a>
)}
</div>
);
}
5 changes: 4 additions & 1 deletion frontend/src/entry.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,13 @@ async function prepareApp() {
}
}

const QUERY_KEYS_TO_IGNORE = ["authenticated", "served-app"];
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error, query) => {
if (!query.queryKey.includes("authenticated")) toast.error(error.message);
if (!QUERY_KEYS_TO_IGNORE.some((key) => query.queryKey.includes(key))) {
toast.error(error.message);
}
},
}),
defaultOptions: {
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/hooks/query/use-served-app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useQuery } from "@tanstack/react-query";
import axios from "axios";

export const useServedApp = () =>
useQuery({
queryKey: ["served-app"],
queryFn: async () => axios.get("http://localhost:4141"),
refetchInterval: 3000,
});
1 change: 1 addition & 0 deletions frontend/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default [
index("routes/_oh.app._index/route.tsx"),
route("browser", "routes/_oh.app.browser.tsx"),
route("jupyter", "routes/_oh.app.jupyter.tsx"),
route("served", "routes/app.tsx"),
]),
]),

Expand Down
7 changes: 7 additions & 0 deletions frontend/src/routes/_oh.app/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useDisclosure } from "@nextui-org/react";
import React from "react";
import { Outlet } from "react-router";
import { useDispatch, useSelector } from "react-redux";
import { FaServer } from "react-icons/fa";
import { Controls } from "#/components/features/controls/controls";
import { RootState } from "#/store";
import { clearMessages } from "#/state/chat-slice";
Expand All @@ -22,6 +23,7 @@ import { useConversationConfig } from "#/hooks/query/use-conversation-config";
import { Container } from "#/components/layout/container";
import Security from "#/components/shared/modals/security/security";
import { CountBadge } from "#/components/layout/count-badge";
import { ServedAppLabel } from "#/components/layout/served-app-label";

function App() {
const { token, gitHubToken } = useAuth();
Expand Down Expand Up @@ -83,6 +85,11 @@ function App() {
labels={[
{ label: "Workspace", to: "", icon: <CodeIcon /> },
{ label: "Jupyter", to: "jupyter", icon: <ListIcon /> },
{
label: <ServedAppLabel />,
to: "served",
icon: <FaServer />,
},
{
label: (
<div className="flex items-center gap-1">
Expand Down
25 changes: 25 additions & 0 deletions frontend/src/routes/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useServedApp } from "#/hooks/query/use-served-app";

function ServedApp() {
const { isSuccess } = useServedApp();

if (!isSuccess) {
return (
<div className="flex items-center justify-center w-full h-full">
<span className="text-4xl text-neutral-400 font-bold">
Nothing to see here.
</span>
</div>
);
}

return (
<iframe
title="Served App"
src="http://localhost:4141"
className="w-full h-full"
/>
);
}

export default ServedApp;
5 changes: 5 additions & 0 deletions openhands/core/config/sandbox_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class SandboxConfig:
browsergym_eval_env: The BrowserGym environment to use for evaluation.
Default is None for general purpose browsing. Check evaluation/miniwob and evaluation/webarena for examples.
platform: The platform on which the image should be built. Default is None.
port_mappings: Custom port mappings from container ports to host ports. Default is empty dict.
This is a dictionary where keys are container ports and values are host ports.
For example, {8080: 8080} maps container port 8080 to host port 8080.
Note: Port 4141 is always mapped to host port 4141 by default.
"""

remote_runtime_api_url: str = 'http://localhost:8000'
Expand All @@ -56,6 +60,7 @@ class SandboxConfig:
browsergym_eval_env: str | None = None
platform: str | None = None
close_delay: int = 15
port_mappings: dict[int, int] = field(default_factory=dict)

def defaults_to_dict(self) -> dict:
"""Serialize fields to a dict for the frontend, including type hints, defaults, and whether it's optional."""
Expand Down
25 changes: 20 additions & 5 deletions openhands/runtime/impl/eventstream/eventstream_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,25 @@ def _init_container(self):
use_host_network = self.config.sandbox.use_host_network
network_mode: str | None = 'host' if use_host_network else None

port_mapping: dict[str, list[dict[str, str]]] | None = (
None
if use_host_network
else {f'{self._container_port}/tcp': [{'HostPort': str(self._host_port)}]}
)
# Initialize port mappings
port_mapping: dict[str, list[dict[str, str]]] | None = None
if not use_host_network:
port_mapping = {
f'{self._container_port}/tcp': [{'HostPort': str(self._host_port)}],
'4141/tcp': [{'HostPort': '4141'}],
}
# Add custom port mappings from config if specified
if (
hasattr(self.config.sandbox, 'port_mappings')
and self.config.sandbox.port_mappings
):
for (
container_port,
host_port,
) in self.config.sandbox.port_mappings.items():
port_mapping[f'{container_port}/tcp'] = [
{'HostPort': str(host_port)}
]

if use_host_network:
self.log(
Expand Down Expand Up @@ -385,6 +399,7 @@ def _init_container(self):
'error',
f'Error: Instance {self.container_name} FAILED to start container!\n',
)
self.log('error', str(e))
except Exception as e:
self.log(
'error',
Expand Down
Loading