12 | {/* Terminal uses some API that is not compatible in a server-environment. For this reason, we lazy load it to ensure
13 | * that it loads only in the client-side. */}
14 | }>
15 |
16 |
17 |
18 |
19 | );
20 | }
21 |
22 | export default TerminalTab;
23 |
--------------------------------------------------------------------------------
/frontend/src/services/agent-state-service.ts:
--------------------------------------------------------------------------------
1 | import ActionType from "#/types/action-type";
2 | import { AgentState } from "#/types/agent-state";
3 |
4 | export const generateAgentStateChangeEvent = (state: AgentState) => ({
5 | action: ActionType.CHANGE_AGENT_STATE,
6 | args: { agent_state: state },
7 | });
8 |
--------------------------------------------------------------------------------
/frontend/src/services/chat-service.ts:
--------------------------------------------------------------------------------
1 | import ActionType from "#/types/action-type";
2 |
3 | export function createChatMessage(
4 | message: string,
5 | image_urls: string[],
6 | timestamp: string,
7 | ) {
8 | const event = {
9 | action: ActionType.MESSAGE,
10 | args: { content: message, image_urls, timestamp },
11 | };
12 | return event;
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/src/services/terminal-service.ts:
--------------------------------------------------------------------------------
1 | import ActionType from "#/types/action-type";
2 |
3 | export function getTerminalCommand(command: string, hidden: boolean = false) {
4 | const event = { action: ActionType.RUN, args: { command, hidden } };
5 | return event;
6 | }
7 |
--------------------------------------------------------------------------------
/frontend/src/state/agent-slice.tsx:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 | import { AgentState } from "#/types/agent-state";
3 |
4 | export const agentSlice = createSlice({
5 | name: "agent",
6 | initialState: {
7 | curAgentState: AgentState.LOADING,
8 | },
9 | reducers: {
10 | setCurrentAgentState: (state, action) => {
11 | state.curAgentState = action.payload;
12 | },
13 | },
14 | });
15 |
16 | export const { setCurrentAgentState } = agentSlice.actions;
17 |
18 | export default agentSlice.reducer;
19 |
--------------------------------------------------------------------------------
/frontend/src/state/file-state-slice.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit";
2 |
3 | type SliceState = { changed: Record }; // Map
4 |
5 | const initialState: SliceState = {
6 | changed: {},
7 | };
8 |
9 | export const fileStateSlice = createSlice({
10 | name: "fileState",
11 | initialState,
12 | reducers: {
13 | setChanged(
14 | state,
15 | action: PayloadAction<{ path: string; changed: boolean }>,
16 | ) {
17 | const { path, changed } = action.payload;
18 | state.changed[path] = changed;
19 | },
20 | },
21 | });
22 |
23 | export const { setChanged } = fileStateSlice.actions;
24 | export default fileStateSlice.reducer;
25 |
--------------------------------------------------------------------------------
/frontend/src/state/status-slice.ts:
--------------------------------------------------------------------------------
1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit";
2 | import { StatusMessage } from "#/types/message";
3 |
4 | const initialStatusMessage: StatusMessage = {
5 | status_update: true,
6 | type: "info",
7 | id: "",
8 | message: "",
9 | };
10 |
11 | export const statusSlice = createSlice({
12 | name: "status",
13 | initialState: {
14 | curStatusMessage: initialStatusMessage,
15 | },
16 | reducers: {
17 | setCurStatusMessage: (state, action: PayloadAction) => {
18 | state.curStatusMessage = action.payload;
19 | },
20 | },
21 | });
22 |
23 | export const { setCurStatusMessage } = statusSlice.actions;
24 |
25 | export default statusSlice.reducer;
26 |
--------------------------------------------------------------------------------
/frontend/src/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | .button-base {
6 | @apply bg-tertiary border border-neutral-600 rounded;
7 | }
8 |
9 | .skeleton {
10 | @apply bg-gray-400 rounded-md animate-pulse;
11 | }
12 |
13 | .skeleton-round {
14 | @apply bg-gray-400 rounded-full animate-pulse;
15 | }
16 |
17 | .heading {
18 | @apply text-[28px] leading-8 -tracking-[0.02em] font-bold text-content-2;
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/types/agent-state.tsx:
--------------------------------------------------------------------------------
1 | export enum AgentState {
2 | LOADING = "loading",
3 | INIT = "init",
4 | RUNNING = "running",
5 | AWAITING_USER_INPUT = "awaiting_user_input",
6 | PAUSED = "paused",
7 | STOPPED = "stopped",
8 | FINISHED = "finished",
9 | REJECTED = "rejected",
10 | ERROR = "error",
11 | RATE_LIMITED = "rate_limited",
12 | AWAITING_USER_CONFIRMATION = "awaiting_user_confirmation",
13 | USER_CONFIRMED = "user_confirmed",
14 | USER_REJECTED = "user_rejected",
15 | }
16 |
17 | export const RUNTIME_INACTIVE_STATES = [
18 | AgentState.LOADING,
19 | AgentState.STOPPED,
20 | AgentState.ERROR,
21 | ];
22 |
--------------------------------------------------------------------------------
/frontend/src/types/config-type.tsx:
--------------------------------------------------------------------------------
1 | enum ArgConfigType {
2 | LLM_MODEL = "LLM_MODEL",
3 | AGENT = "AGENT",
4 | LANGUAGE = "LANGUAGE",
5 | LLM_API_KEY_SET = "LLM_API_KEY_SET",
6 | }
7 |
8 | const SupportedSettings: string[] = [
9 | ArgConfigType.LLM_MODEL,
10 | ArgConfigType.AGENT,
11 | ArgConfigType.LANGUAGE,
12 | ArgConfigType.LLM_API_KEY_SET,
13 | ];
14 |
15 | export { ArgConfigType, SupportedSettings };
16 |
--------------------------------------------------------------------------------
/frontend/src/types/core/index.ts:
--------------------------------------------------------------------------------
1 | import { OpenHandsAction } from "./actions";
2 | import { OpenHandsObservation } from "./observations";
3 | import { OpenHandsVariance } from "./variances";
4 |
5 | export type OpenHandsParsedEvent =
6 | | OpenHandsAction
7 | | OpenHandsObservation
8 | | OpenHandsVariance;
9 |
--------------------------------------------------------------------------------
/frontend/src/types/react-query.d.ts:
--------------------------------------------------------------------------------
1 | import "@tanstack/react-query";
2 | import type { AxiosError } from "axios";
3 |
4 | interface MyMeta extends Record {
5 | disableToast?: boolean;
6 | }
7 |
8 | declare module "@tanstack/react-query" {
9 | interface Register {
10 | defaultError: AxiosError;
11 |
12 | queryMeta: MyMeta;
13 | mutationMeta: MyMeta;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/types/response-type.tsx:
--------------------------------------------------------------------------------
1 | import { ActionMessage, ObservationMessage, StatusMessage } from "./message";
2 |
3 | type SocketMessage = ActionMessage | ObservationMessage | StatusMessage;
4 |
5 | export { type SocketMessage };
6 |
--------------------------------------------------------------------------------
/frontend/src/types/tab-option.tsx:
--------------------------------------------------------------------------------
1 | enum TabOption {
2 | PLANNER = "planner",
3 | BROWSER = "browser",
4 | JUPYTER = "jupyter",
5 | VSCODE = "vscode",
6 | }
7 |
8 | type TabType =
9 | | TabOption.PLANNER
10 | | TabOption.BROWSER
11 | | TabOption.JUPYTER
12 | | TabOption.VSCODE;
13 |
14 | const AllTabs = [
15 | TabOption.VSCODE,
16 | TabOption.BROWSER,
17 | TabOption.PLANNER,
18 | TabOption.JUPYTER,
19 | ];
20 |
21 | export { AllTabs, TabOption, type TabType };
22 |
--------------------------------------------------------------------------------
/frontend/src/utils/amount-is-valid.ts:
--------------------------------------------------------------------------------
1 | const MINIMUM_AMOUNT = 10;
2 | const MAXIMUM_AMOUNT = 25_000;
3 |
4 | export const amountIsValid = (amount: string) => {
5 | const value = parseInt(amount, 10);
6 | if (Number.isNaN(value)) return false;
7 | if (value < 0) return false;
8 | if (value < MINIMUM_AMOUNT) return false;
9 | if (value > MAXIMUM_AMOUNT) return false;
10 | if (value !== parseFloat(amount)) return false; // Ensure it's an integer
11 |
12 | return true;
13 | };
14 |
--------------------------------------------------------------------------------
/frontend/src/utils/beep.tsx:
--------------------------------------------------------------------------------
1 | const beep = () => {
2 | const snd = new Audio("/beep.wav");
3 | snd.addEventListener("canplaythrough", () => snd.play());
4 | };
5 |
6 | export default beep;
7 |
--------------------------------------------------------------------------------
/frontend/src/utils/convert-file-to-text.ts:
--------------------------------------------------------------------------------
1 | export const convertFileToText = async (file: File) => {
2 | const reader = new FileReader();
3 |
4 | return new Promise((resolve) => {
5 | reader.onload = () => {
6 | resolve(reader.result as string);
7 | };
8 | reader.readAsText(file);
9 | });
10 | };
11 |
--------------------------------------------------------------------------------
/frontend/src/utils/convert-image-to-base-64.ts:
--------------------------------------------------------------------------------
1 | export const convertImageToBase64 = (file: File): Promise =>
2 | new Promise((resolve, reject) => {
3 | const reader = new FileReader();
4 | reader.onloadend = () => {
5 | resolve(reader.result as string);
6 | };
7 | reader.onerror = reject;
8 | reader.readAsDataURL(file);
9 | });
10 |
--------------------------------------------------------------------------------
/frontend/src/utils/convert-raw-providers-to-list.ts:
--------------------------------------------------------------------------------
1 | import { Provider } from "#/types/settings";
2 |
3 | export const convertRawProvidersToList = (
4 | raw: Partial> | undefined,
5 | ): Provider[] => {
6 | if (!raw) return [];
7 | const list: Provider[] = [];
8 | for (const key of Object.keys(raw)) {
9 | if (key) {
10 | list.push(key as Provider);
11 | }
12 | }
13 | return list;
14 | };
15 |
--------------------------------------------------------------------------------
/frontend/src/utils/custom-toast-handlers.tsx:
--------------------------------------------------------------------------------
1 | import { CSSProperties } from "react";
2 | import toast, { ToastOptions } from "react-hot-toast";
3 |
4 | const TOAST_STYLE: CSSProperties = {
5 | background: "#454545",
6 | border: "1px solid #717888",
7 | color: "#fff",
8 | borderRadius: "4px",
9 | };
10 |
11 | const TOAST_OPTIONS: ToastOptions = {
12 | position: "top-right",
13 | style: TOAST_STYLE,
14 | };
15 |
16 | export const displayErrorToast = (error: string) => {
17 | toast.error(error, TOAST_OPTIONS);
18 | };
19 |
20 | export const displaySuccessToast = (message: string) => {
21 | toast.success(message, TOAST_OPTIONS);
22 | };
23 |
--------------------------------------------------------------------------------
/frontend/src/utils/download-json.ts:
--------------------------------------------------------------------------------
1 | export const downloadJSON = (data: object, filename: string) => {
2 | const blob = new Blob([JSON.stringify(data, null, 2)], {
3 | type: "application/json",
4 | });
5 | const url = URL.createObjectURL(blob);
6 | const link = document.createElement("a");
7 | link.href = url;
8 | link.download = filename;
9 | document.body.appendChild(link);
10 | link.click();
11 | document.body.removeChild(link);
12 | URL.revokeObjectURL(url);
13 | };
14 |
--------------------------------------------------------------------------------
/frontend/src/utils/extract-next-page-from-link.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Extracts the next page number from a GitHub API link header.
3 | * @param link The GitHub API link header
4 | * @returns The next page number or null if there is no next page
5 | */
6 | export const extractNextPageFromLink = (link: string): number | null => {
7 | const regex = /<[^>]*[?&]page=(\d+)(?:&[^>]*)?>; rel="next"/;
8 | const match = link.match(regex);
9 |
10 | return match ? parseInt(match[1], 10) : null;
11 | };
12 |
--------------------------------------------------------------------------------
/frontend/src/utils/feature-flags.ts:
--------------------------------------------------------------------------------
1 | export function loadFeatureFlag(
2 | flagName: string,
3 | defaultValue: boolean = false,
4 | ): boolean {
5 | try {
6 | const stringValue =
7 | localStorage.getItem(`FEATURE_${flagName}`) || defaultValue.toString();
8 | const value = !!JSON.parse(stringValue);
9 | return value;
10 | } catch (e) {
11 | return defaultValue;
12 | }
13 | }
14 |
15 | export const BILLING_SETTINGS = () => loadFeatureFlag("BILLING_SETTINGS");
16 | export const HIDE_LLM_SETTINGS = () => loadFeatureFlag("HIDE_LLM_SETTINGS");
17 | export const ENABLE_TRAJECTORY_REPLAY = () =>
18 | loadFeatureFlag("TRAJECTORY_REPLAY");
19 |
--------------------------------------------------------------------------------
/frontend/src/utils/format-ms.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Formats a time in milliseconds to the format "mm:ss"
3 | * @param ms The time in milliseconds
4 | * @returns The formatted time in the format "mm:ss"
5 | *
6 | * @example
7 | * formatMs(1000) // "00:01"
8 | * formatMs(1000 * 60) // "01:00"
9 | * formatMs(1000 * 60 * 2.5) // "02:30"
10 | */
11 | export const formatMs = (ms: number) => {
12 | const minutes = Math.floor(ms / 1000 / 60);
13 | const seconds = Math.floor((ms / 1000) % 60);
14 |
15 | return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
16 | };
17 |
--------------------------------------------------------------------------------
/frontend/src/utils/get-random-key.ts:
--------------------------------------------------------------------------------
1 | export const getRandomKey = (obj: Record) => {
2 | const keys = Object.keys(obj);
3 | const randomKey = keys[Math.floor(Math.random() * keys.length)];
4 |
5 | return randomKey;
6 | };
7 |
--------------------------------------------------------------------------------
/frontend/src/utils/gget-formatted-datetime.ts:
--------------------------------------------------------------------------------
1 | export const getFormattedDateTime = () => {
2 | const now = new Date();
3 | const year = now.getFullYear();
4 | const month = String(now.getMonth() + 1).padStart(2, "0");
5 | const day = String(now.getDate()).padStart(2, "0");
6 | const hour = String(now.getHours()).padStart(2, "0");
7 | const minute = String(now.getMinutes()).padStart(2, "0");
8 | const second = String(now.getSeconds()).padStart(2, "0");
9 |
10 | return `${year}-${month}-${day}-${hour}-${minute}-${second}`;
11 | };
12 |
--------------------------------------------------------------------------------
/frontend/src/utils/handle-capture-consent.ts:
--------------------------------------------------------------------------------
1 | import posthog from "posthog-js";
2 |
3 | /**
4 | * Handle user consent for tracking
5 | * @param consent Whether the user consents to tracking
6 | */
7 | export const handleCaptureConsent = (consent: boolean) => {
8 | if (consent && !posthog.has_opted_in_capturing()) {
9 | posthog.opt_in_capturing();
10 | }
11 |
12 | if (!consent && !posthog.has_opted_out_capturing()) {
13 | posthog.opt_out_capturing();
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/frontend/src/utils/has-advanced-settings-set.ts:
--------------------------------------------------------------------------------
1 | import { DEFAULT_SETTINGS } from "#/services/settings";
2 | import { Settings } from "#/types/settings";
3 |
4 | export const hasAdvancedSettingsSet = (settings: Partial): boolean =>
5 | Object.keys(settings).length > 0 &&
6 | (!!settings.LLM_BASE_URL ||
7 | settings.AGENT !== DEFAULT_SETTINGS.AGENT ||
8 | settings.CONFIRMATION_MODE ||
9 | !!settings.SECURITY_ANALYZER);
10 |
--------------------------------------------------------------------------------
/frontend/src/utils/is-number.ts:
--------------------------------------------------------------------------------
1 | export const isNumber = (value: string | number): boolean =>
2 | !Number.isNaN(Number(value));
3 |
--------------------------------------------------------------------------------
/frontend/src/utils/parse-github-url.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Given a GitHub URL of a repository, obtain the owner and repository name
3 | * @param url The GitHub repository URL
4 | * @returns An array containing the owner and repository names
5 | *
6 | * @example
7 | * const parsed = parseGithubUrl("https://github.com/All-Hands-AI/OpenHands");
8 | * console.log(parsed) // ["All-Hands-AI", "OpenHands"]
9 | */
10 | export const parseGithubUrl = (url: string) => {
11 | const parts = url.replace("https://github.com/", "").split("/");
12 | return parts.length === 2 ? parts : [];
13 | };
14 |
--------------------------------------------------------------------------------
/frontend/src/utils/sanitize-query.ts:
--------------------------------------------------------------------------------
1 | export const sanitizeQuery = (query: string) =>
2 | query
3 | .trim()
4 | .replace(/https?:\/\//, "")
5 | .replace(/github.com\//, "")
6 | .replace(/\.git$/, "")
7 | .toLowerCase();
8 |
--------------------------------------------------------------------------------
/frontend/src/utils/suggestions/index.ts:
--------------------------------------------------------------------------------
1 | import { NON_REPO_SUGGESTIONS } from "./non-repo-suggestions";
2 | import { REPO_SUGGESTIONS } from "./repo-suggestions";
3 |
4 | export const SUGGESTIONS: Record<
5 | "repo" | "non-repo",
6 | Record
7 | > = {
8 | repo: REPO_SUGGESTIONS,
9 | "non-repo": NON_REPO_SUGGESTIONS,
10 | };
11 |
--------------------------------------------------------------------------------
/frontend/src/utils/type-guards.ts:
--------------------------------------------------------------------------------
1 | import { AxiosError } from "axios";
2 |
3 | export const isAxiosErrorWithErrorField = (
4 | error: AxiosError,
5 | ): error is AxiosError<{ error: string }> =>
6 | typeof error.response?.data === "object" &&
7 | error.response?.data !== null &&
8 | "error" in error.response.data &&
9 | typeof error.response?.data?.error === "string";
10 |
11 | export const isAxiosErrorWithMessageField = (
12 | error: AxiosError,
13 | ): error is AxiosError<{ message: string }> =>
14 | typeof error.response?.data === "object" &&
15 | error.response?.data !== null &&
16 | "message" in error.response.data &&
17 | typeof error.response?.data?.message === "string";
18 |
--------------------------------------------------------------------------------
/frontend/src/wrapper/event-handler.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useHandleWSEvents } from "../hooks/use-handle-ws-events";
3 | import { useHandleRuntimeActive } from "../hooks/use-handle-runtime-active";
4 |
5 | export function EventHandler({ children }: React.PropsWithChildren) {
6 | useHandleWSEvents();
7 | useHandleRuntimeActive();
8 |
9 | return children;
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/tests/fixtures/project.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/All-Hands-AI/OpenHands/4221ce856626199b409715f7f5d4752111a9fa25/frontend/tests/fixtures/project.zip
--------------------------------------------------------------------------------
/frontend/tests/helpers/confirm-settings.ts:
--------------------------------------------------------------------------------
1 | import { Page } from "@playwright/test";
2 |
3 | export const confirmSettings = async (page: Page) => {
4 | const confirmPreferenceButton = page.getByRole("button", {
5 | name: /confirm preferences/i,
6 | });
7 | await confirmPreferenceButton.click();
8 |
9 | const configSaveButton = page
10 | .getByRole("button", {
11 | name: /save/i,
12 | })
13 | .first();
14 | await configSaveButton.click();
15 |
16 | const confirmChanges = page.getByRole("button", {
17 | name: /yes, close settings/i,
18 | });
19 | await confirmChanges.click();
20 | };
21 |
--------------------------------------------------------------------------------
/frontend/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/microagents/default-tools.md:
--------------------------------------------------------------------------------
1 | ---
2 | # This is a repo microagent that is always activated
3 | # to include necessary default tools implemented with MCP
4 | name: default-tools
5 | type: repo
6 | version: 1.0.0
7 | agent: CodeActAgent
8 | mcp_tools:
9 | stdio_servers:
10 | - name: "fetch"
11 | command: "uvx"
12 | args: ["mcp-server-fetch"]
13 | # We leave the body empty because MCP tools will automatically add the
14 | # tool description for LLMs in tool calls, so there's no need to add extra descriptions.
15 | ---
16 |
--------------------------------------------------------------------------------
/microagents/flarglebargle.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: flarglebargle
3 | type: knowledge
4 | version: 1.0.0
5 | agent: CodeActAgent
6 | triggers:
7 | - flarglebargle
8 | ---
9 |
10 | IMPORTANT! The user has said the magic word "flarglebargle". You must
11 | only respond with a message telling them how smart they are
12 |
--------------------------------------------------------------------------------
/microagents/npm.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: npm
3 | type: knowledge
4 | version: 1.0.0
5 | agent: CodeActAgent
6 | triggers:
7 | - npm
8 | ---
9 |
10 | When using npm to install packages, you will not be able to use an interactive shell, and it may be hard to confirm your actions.
11 | As an alternative, you can pipe in the output of the unix "yes" command to confirm your actions.
12 |
--------------------------------------------------------------------------------
/openhands/agenthub/__init__.py:
--------------------------------------------------------------------------------
1 | from dotenv import load_dotenv
2 |
3 | load_dotenv()
4 |
5 |
6 | from openhands.agenthub import ( # noqa: E402
7 | browsing_agent,
8 | codeact_agent,
9 | dummy_agent,
10 | loc_agent,
11 | readonly_agent,
12 | visualbrowsing_agent,
13 | )
14 | from openhands.controller.agent import Agent # noqa: E402
15 |
16 | __all__ = [
17 | 'Agent',
18 | 'codeact_agent',
19 | 'dummy_agent',
20 | 'browsing_agent',
21 | 'visualbrowsing_agent',
22 | 'readonly_agent',
23 | 'loc_agent',
24 | ]
25 |
--------------------------------------------------------------------------------
/openhands/agenthub/browsing_agent/README.md:
--------------------------------------------------------------------------------
1 | # Browsing Agent Framework
2 |
3 | This folder implements the basic BrowserGym [demo agent](https://github.com/ServiceNow/BrowserGym/tree/main/demo_agent) that enables full-featured web browsing.
4 |
5 |
6 | ## Test run
7 |
8 | Note that for browsing tasks, GPT-4 is usually a requirement to get reasonable results, due to the complexity of the web page structures.
9 |
10 | ```
11 | poetry run python ./openhands/core/main.py \
12 | -i 10 \
13 | -t "tell me the usa's president using google search" \
14 | -c BrowsingAgent \
15 | -m claude-3-5-sonnet-20241022
16 | ```
17 |
--------------------------------------------------------------------------------
/openhands/agenthub/browsing_agent/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.agenthub.browsing_agent.browsing_agent import BrowsingAgent
2 | from openhands.controller.agent import Agent
3 |
4 | Agent.register('BrowsingAgent', BrowsingAgent)
5 |
--------------------------------------------------------------------------------
/openhands/agenthub/codeact_agent/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.agenthub.codeact_agent.codeact_agent import CodeActAgent
2 | from openhands.controller.agent import Agent
3 |
4 | Agent.register('CodeActAgent', CodeActAgent)
5 |
--------------------------------------------------------------------------------
/openhands/agenthub/codeact_agent/prompts/in_context_learning_example_suffix.j2:
--------------------------------------------------------------------------------
1 | --------------------- END OF NEW TASK DESCRIPTION ---------------------
2 |
3 | PLEASE follow the format strictly! PLEASE EMIT ONE AND ONLY ONE FUNCTION CALL PER MESSAGE.
4 |
--------------------------------------------------------------------------------
/openhands/agenthub/codeact_agent/prompts/microagent_info.j2:
--------------------------------------------------------------------------------
1 | {% for agent_info in triggered_agents %}
2 |
3 | The following information has been included based on a keyword match for "{{ agent_info.trigger }}".
4 | It may or may not be relevant to the user's request.
5 |
6 | {{ agent_info.content }}
7 |
8 | {% endfor %}
9 |
--------------------------------------------------------------------------------
/openhands/agenthub/codeact_agent/prompts/user_prompt.j2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/All-Hands-AI/OpenHands/4221ce856626199b409715f7f5d4752111a9fa25/openhands/agenthub/codeact_agent/prompts/user_prompt.j2
--------------------------------------------------------------------------------
/openhands/agenthub/codeact_agent/tools/__init__.py:
--------------------------------------------------------------------------------
1 | from .bash import create_cmd_run_tool
2 | from .browser import BrowserTool
3 | from .finish import FinishTool
4 | from .ipython import IPythonTool
5 | from .llm_based_edit import LLMBasedFileEditTool
6 | from .str_replace_editor import create_str_replace_editor_tool
7 | from .think import ThinkTool
8 |
9 | __all__ = [
10 | 'BrowserTool',
11 | 'create_cmd_run_tool',
12 | 'FinishTool',
13 | 'IPythonTool',
14 | 'LLMBasedFileEditTool',
15 | 'create_str_replace_editor_tool',
16 | 'ThinkTool',
17 | ]
18 |
--------------------------------------------------------------------------------
/openhands/agenthub/dummy_agent/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.agenthub.dummy_agent.agent import DummyAgent
2 | from openhands.controller.agent import Agent
3 |
4 | Agent.register('DummyAgent', DummyAgent)
5 |
--------------------------------------------------------------------------------
/openhands/agenthub/loc_agent/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.agenthub.loc_agent.loc_agent import LocAgent
2 | from openhands.controller.agent import Agent
3 |
4 | Agent.register('LocAgent', LocAgent)
5 |
--------------------------------------------------------------------------------
/openhands/agenthub/loc_agent/tools/__init__.py:
--------------------------------------------------------------------------------
1 | from .explore_structure import create_explore_tree_structure_tool
2 | from .search_content import SearchEntityTool, SearchRepoTool
3 |
4 | __all__ = [
5 | 'SearchEntityTool',
6 | 'SearchRepoTool',
7 | 'create_explore_tree_structure_tool',
8 | ]
9 |
--------------------------------------------------------------------------------
/openhands/agenthub/readonly_agent/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.agenthub.readonly_agent.readonly_agent import ReadOnlyAgent
2 | from openhands.controller.agent import Agent
3 |
4 | Agent.register('ReadOnlyAgent', ReadOnlyAgent)
5 |
--------------------------------------------------------------------------------
/openhands/agenthub/readonly_agent/prompts/in_context_learning_example_suffix.j2:
--------------------------------------------------------------------------------
1 | --------------------- END OF NEW TASK DESCRIPTION ---------------------
2 |
3 | PLEASE follow the format strictly! PLEASE EMIT ONE AND ONLY ONE FUNCTION CALL PER MESSAGE.
4 |
--------------------------------------------------------------------------------
/openhands/agenthub/readonly_agent/prompts/microagent_info.j2:
--------------------------------------------------------------------------------
1 | {% for agent_info in triggered_agents %}
2 |
3 | The following information has been included based on a keyword match for "{{ agent_info.trigger }}".
4 | It may or may not be relevant to the user's request.
5 |
6 | {{ agent_info.content }}
7 |
8 | {% endfor %}
9 |
--------------------------------------------------------------------------------
/openhands/agenthub/readonly_agent/prompts/user_prompt.j2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/All-Hands-AI/OpenHands/4221ce856626199b409715f7f5d4752111a9fa25/openhands/agenthub/readonly_agent/prompts/user_prompt.j2
--------------------------------------------------------------------------------
/openhands/agenthub/readonly_agent/tools/__init__.py:
--------------------------------------------------------------------------------
1 | """Tools for the ReadOnlyAgent.
2 |
3 | This module defines the read-only tools for the ReadOnlyAgent.
4 | """
5 |
6 | from .glob import GlobTool
7 | from .grep import GrepTool
8 | from .view import ViewTool
9 |
10 | __all__ = [
11 | 'ViewTool',
12 | 'GrepTool',
13 | 'GlobTool',
14 | ]
15 |
16 | # Define the list of read-only tools
17 | READ_ONLY_TOOLS = [
18 | ViewTool,
19 | GrepTool,
20 | GlobTool,
21 | ]
22 |
--------------------------------------------------------------------------------
/openhands/agenthub/visualbrowsing_agent/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.agenthub.visualbrowsing_agent.visualbrowsing_agent import (
2 | VisualBrowsingAgent,
3 | )
4 | from openhands.controller.agent import Agent
5 |
6 | Agent.register('VisualBrowsingAgent', VisualBrowsingAgent)
7 |
--------------------------------------------------------------------------------
/openhands/controller/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.controller.agent_controller import AgentController
2 |
3 | __all__ = [
4 | 'AgentController',
5 | ]
6 |
--------------------------------------------------------------------------------
/openhands/core/const/guide_url.py:
--------------------------------------------------------------------------------
1 | TROUBLESHOOTING_URL = 'https://docs.all-hands.dev/usage/troubleshooting'
2 |
--------------------------------------------------------------------------------
/openhands/core/download.py:
--------------------------------------------------------------------------------
1 | # Run this file to trigger a model download
2 | import openhands.agenthub # noqa F401 (we import this to get the agents registered)
3 |
--------------------------------------------------------------------------------
/openhands/core/schema/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.core.schema.action import ActionType
2 | from openhands.core.schema.agent import AgentState
3 | from openhands.core.schema.observation import ObservationType
4 |
5 | __all__ = [
6 | 'ActionType',
7 | 'ObservationType',
8 | 'AgentState',
9 | ]
10 |
--------------------------------------------------------------------------------
/openhands/critic/__init__.py:
--------------------------------------------------------------------------------
1 | from .base import BaseCritic, CriticResult
2 | from .finish_critic import AgentFinishedCritic
3 |
4 | __all__ = ['CriticResult', 'BaseCritic', 'AgentFinishedCritic']
5 |
--------------------------------------------------------------------------------
/openhands/events/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.events.event import Event, EventSource, RecallType
2 | from openhands.events.stream import EventStream, EventStreamSubscriber
3 |
4 | __all__ = [
5 | 'Event',
6 | 'EventSource',
7 | 'EventStream',
8 | 'EventStreamSubscriber',
9 | 'RecallType',
10 | ]
11 |
--------------------------------------------------------------------------------
/openhands/events/action/action.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from enum import Enum
3 | from typing import ClassVar
4 |
5 | from openhands.events.event import Event
6 |
7 |
8 | class ActionConfirmationStatus(str, Enum):
9 | CONFIRMED = 'confirmed'
10 | REJECTED = 'rejected'
11 | AWAITING_CONFIRMATION = 'awaiting_confirmation'
12 |
13 |
14 | class ActionSecurityRisk(int, Enum):
15 | UNKNOWN = -1
16 | LOW = 0
17 | MEDIUM = 1
18 | HIGH = 2
19 |
20 |
21 | @dataclass
22 | class Action(Event):
23 | runnable: ClassVar[bool] = False
24 |
--------------------------------------------------------------------------------
/openhands/events/action/empty.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 |
3 | from openhands.core.schema import ActionType
4 | from openhands.events.action.action import Action
5 |
6 |
7 | @dataclass
8 | class NullAction(Action):
9 | """An action that does nothing."""
10 |
11 | action: str = ActionType.NULL
12 |
13 | @property
14 | def message(self) -> str:
15 | return 'No action'
16 |
--------------------------------------------------------------------------------
/openhands/events/observation/delegate.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 |
3 | from openhands.core.schema import ObservationType
4 | from openhands.events.observation.observation import Observation
5 |
6 |
7 | @dataclass
8 | class AgentDelegateObservation(Observation):
9 | """This data class represents the result from delegating to another agent.
10 |
11 | Attributes:
12 | content (str): The content of the observation.
13 | outputs (dict): The outputs of the delegated agent.
14 | observation (str): The type of observation.
15 | """
16 |
17 | outputs: dict
18 | observation: str = ObservationType.DELEGATE
19 |
20 | @property
21 | def message(self) -> str:
22 | return ''
23 |
--------------------------------------------------------------------------------
/openhands/events/observation/empty.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 |
3 | from openhands.core.schema import ObservationType
4 | from openhands.events.observation.observation import Observation
5 |
6 |
7 | @dataclass
8 | class NullObservation(Observation):
9 | """This data class represents a null observation.
10 | This is used when the produced action is NOT executable.
11 | """
12 |
13 | observation: str = ObservationType.NULL
14 |
15 | @property
16 | def message(self) -> str:
17 | return 'No observation'
18 |
--------------------------------------------------------------------------------
/openhands/events/observation/error.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 |
3 | from openhands.core.schema import ObservationType
4 | from openhands.events.observation.observation import Observation
5 |
6 |
7 | @dataclass
8 | class ErrorObservation(Observation):
9 | """This data class represents an error encountered by the agent.
10 |
11 | This is the type of error that LLM can recover from.
12 | E.g., Linter error after editing a file.
13 | """
14 |
15 | observation: str = ObservationType.ERROR
16 | error_id: str = ''
17 |
18 | @property
19 | def message(self) -> str:
20 | return self.content
21 |
22 | def __str__(self) -> str:
23 | return f'**ErrorObservation**\n{self.content}'
24 |
--------------------------------------------------------------------------------
/openhands/events/observation/mcp.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass, field
2 | from typing import Any
3 |
4 | from openhands.core.schema import ObservationType
5 | from openhands.events.observation.observation import Observation
6 |
7 |
8 | @dataclass
9 | class MCPObservation(Observation):
10 | """This data class represents the result of a MCP Server operation."""
11 |
12 | observation: str = ObservationType.MCP
13 | name: str = '' # The name of the MCP tool that was called
14 | arguments: dict[str, Any] = field(
15 | default_factory=dict
16 | ) # The arguments passed to the MCP tool
17 |
18 | @property
19 | def message(self) -> str:
20 | return self.content
21 |
--------------------------------------------------------------------------------
/openhands/events/observation/observation.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 |
3 | from openhands.events.event import Event
4 |
5 |
6 | @dataclass
7 | class Observation(Event):
8 | content: str
9 |
--------------------------------------------------------------------------------
/openhands/events/observation/reject.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 |
3 | from openhands.core.schema import ObservationType
4 | from openhands.events.observation.observation import Observation
5 |
6 |
7 | @dataclass
8 | class UserRejectObservation(Observation):
9 | """This data class represents the result of a rejected action."""
10 |
11 | observation: str = ObservationType.USER_REJECTED
12 |
13 | @property
14 | def message(self) -> str:
15 | return self.content
16 |
--------------------------------------------------------------------------------
/openhands/events/observation/success.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 |
3 | from openhands.core.schema import ObservationType
4 | from openhands.events.observation.observation import Observation
5 |
6 |
7 | @dataclass
8 | class SuccessObservation(Observation):
9 | """This data class represents the result of a successful action."""
10 |
11 | observation: str = ObservationType.SUCCESS
12 |
13 | @property
14 | def message(self) -> str:
15 | return self.content
16 |
--------------------------------------------------------------------------------
/openhands/events/serialization/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.events.serialization.action import (
2 | action_from_dict,
3 | )
4 | from openhands.events.serialization.event import (
5 | event_from_dict,
6 | event_to_dict,
7 | event_to_trajectory,
8 | )
9 | from openhands.events.serialization.observation import (
10 | observation_from_dict,
11 | )
12 |
13 | __all__ = [
14 | 'action_from_dict',
15 | 'event_from_dict',
16 | 'event_to_dict',
17 | 'event_to_trajectory',
18 | 'observation_from_dict',
19 | ]
20 |
--------------------------------------------------------------------------------
/openhands/events/tool.py:
--------------------------------------------------------------------------------
1 | from litellm import ModelResponse
2 | from pydantic import BaseModel
3 |
4 |
5 | class ToolCallMetadata(BaseModel):
6 | # See https://docs.litellm.ai/docs/completion/function_call#step-3---second-litellmcompletion-call
7 | function_name: str # Name of the function that was called
8 | tool_call_id: str # ID of the tool call
9 |
10 | model_response: ModelResponse
11 | total_calls_in_response: int
12 |
--------------------------------------------------------------------------------
/openhands/integrations/templates/resolver/github/issue_comment_prompt.j2:
--------------------------------------------------------------------------------
1 | {{ issue_comment }}
2 |
--------------------------------------------------------------------------------
/openhands/integrations/templates/resolver/github/issue_labeled_prompt.j2:
--------------------------------------------------------------------------------
1 | Please fix issue number #{{ issue_number }} in your repository.
2 |
--------------------------------------------------------------------------------
/openhands/integrations/templates/resolver/github/pr_update_prompt.j2:
--------------------------------------------------------------------------------
1 | {{ pr_comment }}
2 |
--------------------------------------------------------------------------------
/openhands/integrations/templates/resolver/gitlab/issue_comment_prompt.j2:
--------------------------------------------------------------------------------
1 | {{ issue_comment }}
--------------------------------------------------------------------------------
/openhands/integrations/templates/resolver/gitlab/issue_labeled_prompt.j2:
--------------------------------------------------------------------------------
1 | Please fix issue number #{{ issue_number }} in your repository.
--------------------------------------------------------------------------------
/openhands/integrations/templates/resolver/gitlab/mr_update_prompt.j2:
--------------------------------------------------------------------------------
1 | {{ mr_comment }}
2 |
--------------------------------------------------------------------------------
/openhands/integrations/templates/resolver/slack/user_message_conversation_instructions.j2:
--------------------------------------------------------------------------------
1 | When performing your task, make sure to reference the additional context if needed.
2 | These are a list of text messages attached in order of most recent.
3 |
4 | {% for message in messages %}
5 | {{ message }}
6 | {% if not loop.last %}\n\n{% endif %}
7 | {% endfor %}
8 |
9 |
10 | If you opened a pull request, please leave the following comment at the end your summary and pull request description
11 | `{{ username }} can click here to [continue refining the PR]({{ conversation_url }})`
12 |
--------------------------------------------------------------------------------
/openhands/integrations/templates/resolver/summary_prompt.j2:
--------------------------------------------------------------------------------
1 | Please summarize your work.
2 |
3 | If you answered a question, please re-state the answer to the question
4 | If you made changes, please create a concise overview on whether the request has been addressed successfully or if there are were issues with the attempt.
5 | If successful, make sure your changes are pushed to the remote branch.
6 |
--------------------------------------------------------------------------------
/openhands/integrations/templates/suggested_task/merge_conflict_prompt.j2:
--------------------------------------------------------------------------------
1 | You are working on {{ requestType }} #{{ issue_number }} in repository {{ repo }}. You need to fix the merge conflicts.
2 | Use the {{ apiName }} with the {{ tokenEnvVar }} environment variable to retrieve the {{ requestTypeShort }} details.
3 | Check out the branch from that {{ requestVerb }} and look at the diff versus the base branch of the {{ requestTypeShort }} to understand the {{ requestTypeShort }}'s intention.
4 | Then resolve the merge conflicts. If you aren't sure what the right solution is, look back through the commit history at the commits that introduced the conflict and resolve them accordingly.
5 |
--------------------------------------------------------------------------------
/openhands/integrations/templates/suggested_task/open_issue_prompt.j2:
--------------------------------------------------------------------------------
1 | You are working on Issue #{{ issue_number }} in repository {{ repo }}. Your goal is to fix the issue.
2 | Use the {{ apiName }} with the {{ tokenEnvVar }} environment variable to retrieve the issue details and any comments on the issue.
3 | Then check out a new branch and investigate what changes will need to be made.
4 | Finally, make the required changes and open up a {{ requestVerb }}. Be sure to reference the issue in the {{ requestTypeShort }} description.
5 |
--------------------------------------------------------------------------------
/openhands/integrations/templates/suggested_task/unresolved_comments_prompt.j2:
--------------------------------------------------------------------------------
1 | You are working on {{ requestType }} #{{ issue_number }} in repository {{ repo }}. You need to resolve the remaining comments from reviewers.
2 | Use the {{ apiName }} with the {{ tokenEnvVar }} environment variable to retrieve the {{ requestTypeShort }} details.
3 | Check out the branch from that {{ requestVerb }} and look at the diff versus the base branch of the {{ requestTypeShort }} to understand the {{ requestTypeShort }}'s intention.
4 | Then use the {{ apiName }} to retrieve all the feedback on the {{ requestTypeShort }} so far.
5 | If anything hasn't been addressed, address it and commit your changes back to the same branch.
6 |
--------------------------------------------------------------------------------
/openhands/io/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.io.io import read_input, read_task, read_task_from_file
2 | from openhands.io.json import dumps, loads
3 |
4 | __all__ = [
5 | 'read_input',
6 | 'read_task_from_file',
7 | 'read_task',
8 | 'dumps',
9 | 'loads',
10 | ]
11 |
--------------------------------------------------------------------------------
/openhands/linter/__init__.py:
--------------------------------------------------------------------------------
1 | """Linter module for OpenHands.
2 |
3 | Part of this Linter module is adapted from Aider (Apache 2.0 License, [original
4 | code](https://github.com/paul-gauthier/aider/blob/main/aider/linter.py)).
5 | - Please see the [original repository](https://github.com/paul-gauthier/aider) for more information.
6 | - The detailed implementation of the linter can be found at: https://github.com/All-Hands-AI/openhands-aci.
7 | """
8 |
9 | from openhands_aci.linter import DefaultLinter, LintResult
10 |
11 | __all__ = ['DefaultLinter', 'LintResult']
12 |
--------------------------------------------------------------------------------
/openhands/llm/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.llm.async_llm import AsyncLLM
2 | from openhands.llm.llm import LLM
3 | from openhands.llm.streaming_llm import StreamingLLM
4 |
5 | __all__ = ['LLM', 'AsyncLLM', 'StreamingLLM']
6 |
--------------------------------------------------------------------------------
/openhands/llm/tool_names.py:
--------------------------------------------------------------------------------
1 | """Constants for tool names used in function calling."""
2 |
3 | EXECUTE_BASH_TOOL_NAME = 'execute_bash'
4 | STR_REPLACE_EDITOR_TOOL_NAME = 'str_replace_editor'
5 | BROWSER_TOOL_NAME = 'browser'
6 | FINISH_TOOL_NAME = 'finish'
7 |
--------------------------------------------------------------------------------
/openhands/mcp/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.mcp.client import MCPClient
2 | from openhands.mcp.tool import MCPClientTool
3 | from openhands.mcp.utils import (
4 | add_mcp_tools_to_agent,
5 | call_tool_mcp,
6 | convert_mcp_clients_to_tools,
7 | create_mcp_clients,
8 | fetch_mcp_tools_from_config,
9 | )
10 |
11 | __all__ = [
12 | 'MCPClient',
13 | 'convert_mcp_clients_to_tools',
14 | 'create_mcp_clients',
15 | 'MCPClientTool',
16 | 'fetch_mcp_tools_from_config',
17 | 'call_tool_mcp',
18 | 'add_mcp_tools_to_agent',
19 | ]
20 |
--------------------------------------------------------------------------------
/openhands/memory/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/All-Hands-AI/OpenHands/4221ce856626199b409715f7f5d4752111a9fa25/openhands/memory/__init__.py
--------------------------------------------------------------------------------
/openhands/memory/condenser/__init__.py:
--------------------------------------------------------------------------------
1 | import openhands.memory.condenser.impl # noqa F401 (we import this to get the condensers registered)
2 | from openhands.memory.condenser.condenser import (
3 | Condenser,
4 | get_condensation_metadata,
5 | View,
6 | Condensation,
7 | )
8 |
9 | __all__ = [
10 | 'Condenser',
11 | 'get_condensation_metadata',
12 | 'CONDENSER_REGISTRY',
13 | 'View',
14 | 'Condensation',
15 | ]
16 |
--------------------------------------------------------------------------------
/openhands/memory/condenser/impl/no_op_condenser.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from openhands.core.config.condenser_config import NoOpCondenserConfig
4 | from openhands.memory.condenser.condenser import Condensation, Condenser, View
5 |
6 |
7 | class NoOpCondenser(Condenser):
8 | """A condenser that does nothing to the event sequence."""
9 |
10 | def condense(self, view: View) -> View | Condensation:
11 | """Returns the list of events unchanged."""
12 | return view
13 |
14 | @classmethod
15 | def from_config(cls, config: NoOpCondenserConfig) -> NoOpCondenser:
16 | return NoOpCondenser()
17 |
18 |
19 | NoOpCondenser.register_config(NoOpCondenserConfig)
20 |
--------------------------------------------------------------------------------
/openhands/microagent/__init__.py:
--------------------------------------------------------------------------------
1 | from .microagent import (
2 | BaseMicroagent,
3 | KnowledgeMicroagent,
4 | RepoMicroagent,
5 | load_microagents_from_dir,
6 | )
7 | from .types import MicroagentMetadata, MicroagentType
8 |
9 | __all__ = [
10 | 'BaseMicroagent',
11 | 'KnowledgeMicroagent',
12 | 'RepoMicroagent',
13 | 'MicroagentMetadata',
14 | 'MicroagentType',
15 | 'load_microagents_from_dir',
16 | ]
17 |
--------------------------------------------------------------------------------
/openhands/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/All-Hands-AI/OpenHands/4221ce856626199b409715f7f5d4752111a9fa25/openhands/py.typed
--------------------------------------------------------------------------------
/openhands/resolver/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/All-Hands-AI/OpenHands/4221ce856626199b409715f7f5d4752111a9fa25/openhands/resolver/__init__.py
--------------------------------------------------------------------------------
/openhands/resolver/io_utils.py:
--------------------------------------------------------------------------------
1 | import json
2 | from typing import Iterable
3 |
4 | from openhands.resolver.resolver_output import ResolverOutput
5 |
6 |
7 | def load_all_resolver_outputs(output_jsonl: str) -> Iterable[ResolverOutput]:
8 | with open(output_jsonl, 'r') as f:
9 | for line in f:
10 | yield ResolverOutput.model_validate(json.loads(line))
11 |
12 |
13 | def load_single_resolver_output(output_jsonl: str, issue_number: int) -> ResolverOutput:
14 | for resolver_output in load_all_resolver_outputs(output_jsonl):
15 | if resolver_output.issue.number == issue_number:
16 | return resolver_output
17 | raise ValueError(f'Issue number {issue_number} not found in {output_jsonl}')
18 |
--------------------------------------------------------------------------------
/openhands/resolver/patching/README.md:
--------------------------------------------------------------------------------
1 | # Patching code
2 |
3 | Originally from [whatthepatch](https://github.com/cscorley/whatthepatch)
4 | (MIT license)
5 |
--------------------------------------------------------------------------------
/openhands/resolver/patching/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from .apply import apply_diff
4 | from .patch import parse_patch
5 |
6 | __all__ = ['parse_patch', 'apply_diff']
7 |
--------------------------------------------------------------------------------
/openhands/resolver/prompts/repo_instructions/all-hands-ai___openhands-resolver.txt:
--------------------------------------------------------------------------------
1 | This is a Python repo for openhands-resolver, a library that attempts to resolve github issues with the AI agent OpenHands.
2 |
3 | - Setup: `poetry install --with test --with dev`
4 | - Testing: `poetry run pytest tests/test_*.py`
5 |
--------------------------------------------------------------------------------
/openhands/resolver/prompts/repo_instructions/all-hands-ai___openhands.txt:
--------------------------------------------------------------------------------
1 | OpenHands is an automated AI software engineer. It is a repo with a Python backend
2 | (in the `openhands` directory) and typescript frontend (in the `frontend` directory).
3 |
4 | - Setup: To set up the repo, including frontend/backend you can `make build`
5 | - Backend Testing: All tests are in `tests/unit/test_*.py`. To test new code, you
6 | can do `poetry run pytest tests/unit/test_xxx.py` where `xxx` is the appropriate
7 | file for the current functionality. Write all tests with pytest.
8 |
--------------------------------------------------------------------------------
/openhands/resolver/prompts/repo_instructions/rbren___rss-parser.txt:
--------------------------------------------------------------------------------
1 | This is a node repo for an RSS parser.
2 | - Setup: `yes | npm install`
3 | - Testing: `SKIP_BROWSER_TESTS=1 npm test`
4 | - Writing Tests: Add to the `test` directory.
5 |
--------------------------------------------------------------------------------
/openhands/resolver/prompts/resolve/basic-conversation-instructions.jinja:
--------------------------------------------------------------------------------
1 | IMPORTANT: You should ONLY interact with the environment provided to you AND NEVER ASK FOR HUMAN HELP.
2 | You SHOULD INCLUDE PROPER INDENTATION in your edit commands.{% if repo_instruction %}
3 |
4 | Some basic information about this repository:
5 | {{ repo_instruction }}{% endif %}
6 |
7 | When you think you have fixed the issue through code changes, please finish the interaction.
8 |
--------------------------------------------------------------------------------
/openhands/resolver/prompts/resolve/basic-followup.jinja:
--------------------------------------------------------------------------------
1 | Please fix the code based on the following feedback
2 |
3 | # Review comments
4 | {{ review_comments }}
5 |
6 | # Review threads
7 | {{ review_threads }}
8 |
9 | # Review thread files
10 | {{ files }}
11 |
12 | # PR Thread Comments
13 | {{ thread_context }}
14 |
--------------------------------------------------------------------------------
/openhands/resolver/prompts/resolve/basic-with-tests.jinja:
--------------------------------------------------------------------------------
1 | Please fix the following issue for the repository in /workspace.
2 | An environment has been set up for you to start working. You may assume all necessary tools are installed.
3 |
4 | # Problem Statement
5 | {{ body }}
6 |
--------------------------------------------------------------------------------
/openhands/resolver/prompts/resolve/basic.jinja:
--------------------------------------------------------------------------------
1 | Please fix the following issue for the repository in /workspace.
2 | An environment has been set up for you to start working. You may assume all necessary tools are installed.
3 |
4 | # Problem Statement
5 | {{ body }}
6 |
--------------------------------------------------------------------------------
/openhands/resolver/prompts/resolve/pr-changes-summary.jinja:
--------------------------------------------------------------------------------
1 | Please create a concise overview of the following changes, commenting on whether all issues have been successfully resolved or if there are still issues remaining:
2 |
3 | {{ comment_message }}
4 |
--------------------------------------------------------------------------------
/openhands/resolver/resolver_output.py:
--------------------------------------------------------------------------------
1 | from typing import Any
2 |
3 | from litellm import BaseModel
4 |
5 | from openhands.resolver.interfaces.issue import Issue
6 |
7 |
8 | class ResolverOutput(BaseModel):
9 | # NOTE: User-specified
10 | issue: Issue
11 | issue_type: str
12 | instruction: str
13 | base_commit: str
14 | git_patch: str
15 | history: list[dict[str, Any]]
16 | metrics: dict[str, Any] | None
17 | success: bool
18 | comment_success: list[bool] | None
19 | result_explanation: str
20 | error: str | None
21 |
--------------------------------------------------------------------------------
/openhands/runtime/browser/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.runtime.browser.utils import browse
2 |
3 | __all__ = ['browse']
4 |
--------------------------------------------------------------------------------
/openhands/runtime/builder/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.runtime.builder.base import RuntimeBuilder
2 | from openhands.runtime.builder.docker import DockerRuntimeBuilder
3 |
4 | __all__ = ['RuntimeBuilder', 'DockerRuntimeBuilder']
5 |
--------------------------------------------------------------------------------
/openhands/runtime/impl/cli/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | CLI Runtime implementation for OpenHands.
3 | """
4 |
5 | from openhands.runtime.impl.cli.cli_runtime import CLIRuntime
6 |
7 | __all__ = ['CLIRuntime']
8 |
--------------------------------------------------------------------------------
/openhands/runtime/impl/docker/containers.py:
--------------------------------------------------------------------------------
1 | import docker
2 |
3 |
4 | def stop_all_containers(prefix: str) -> None:
5 | docker_client = docker.from_env()
6 | try:
7 | containers = docker_client.containers.list(all=True)
8 | for container in containers:
9 | try:
10 | if container.name.startswith(prefix):
11 | container.stop()
12 | except docker.errors.APIError:
13 | pass
14 | except docker.errors.NotFound:
15 | pass
16 | except docker.errors.NotFound: # yes, this can happen!
17 | pass
18 | finally:
19 | docker_client.close()
20 |
--------------------------------------------------------------------------------
/openhands/runtime/impl/local/__init__.py:
--------------------------------------------------------------------------------
1 | """Local runtime implementation."""
2 |
3 | from openhands.runtime.impl.local.local_runtime import LocalRuntime
4 |
5 | __all__ = ['LocalRuntime']
6 |
--------------------------------------------------------------------------------
/openhands/runtime/mcp/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "default": []
3 | }
4 |
--------------------------------------------------------------------------------
/openhands/runtime/plugins/agent_skills/file_editor/README.md:
--------------------------------------------------------------------------------
1 | # File Editor
2 |
3 | This file editor is largely based on Anthorpic released [`str_replace_editor`](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo/computer_use_demo/tools/edit.py). The original code was released under [MIT license](https://github.com/anthropics/anthropic-quickstarts/blob/e373524f07594d48c3f9563248ea282a4c306c0c/LICENSE).
4 |
--------------------------------------------------------------------------------
/openhands/runtime/plugins/agent_skills/file_editor/__init__.py:
--------------------------------------------------------------------------------
1 | """This file imports a global singleton of the `EditTool` class as well as raw functions that expose
2 | its __call__.
3 | The implementation of the `EditTool` class can be found at: https://github.com/All-Hands-AI/openhands-aci/.
4 | """
5 |
6 | from openhands_aci.editor import file_editor
7 |
8 | __all__ = ['file_editor']
9 |
--------------------------------------------------------------------------------
/openhands/runtime/plugins/agent_skills/file_ops/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.runtime.plugins.agent_skills.file_ops import file_ops
2 | from openhands.runtime.plugins.agent_skills.utils.dependency import import_functions
3 |
4 | import_functions(
5 | module=file_ops, function_names=file_ops.__all__, target_globals=globals()
6 | )
7 | __all__ = file_ops.__all__
8 |
--------------------------------------------------------------------------------
/openhands/runtime/plugins/agent_skills/file_reader/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.runtime.plugins.agent_skills.file_reader import file_readers
2 | from openhands.runtime.plugins.agent_skills.utils.dependency import import_functions
3 |
4 | import_functions(
5 | module=file_readers, function_names=file_readers.__all__, target_globals=globals()
6 | )
7 | __all__ = file_readers.__all__
8 |
--------------------------------------------------------------------------------
/openhands/runtime/plugins/agent_skills/repo_ops/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.runtime.plugins.agent_skills.repo_ops import repo_ops
2 | from openhands.runtime.plugins.agent_skills.utils.dependency import import_functions
3 |
4 | import_functions(
5 | module=repo_ops, function_names=repo_ops.__all__, target_globals=globals()
6 | )
7 | __all__ = repo_ops.__all__
8 |
--------------------------------------------------------------------------------
/openhands/runtime/plugins/agent_skills/repo_ops/repo_ops.py:
--------------------------------------------------------------------------------
1 | from openhands_aci.indexing.locagent.tools import (
2 | explore_tree_structure,
3 | get_entity_contents,
4 | search_code_snippets,
5 | )
6 |
7 | __all__ = [
8 | 'get_entity_contents',
9 | 'search_code_snippets',
10 | 'explore_tree_structure',
11 | ]
12 |
--------------------------------------------------------------------------------
/openhands/runtime/plugins/agent_skills/utils/dependency.py:
--------------------------------------------------------------------------------
1 | from types import ModuleType
2 |
3 |
4 | def import_functions(
5 | module: ModuleType, function_names: list[str], target_globals: dict[str, object]
6 | ) -> None:
7 | for name in function_names:
8 | if hasattr(module, name):
9 | target_globals[name] = getattr(module, name)
10 | else:
11 | raise ValueError(f'Function {name} not found in {module.__name__}')
12 |
--------------------------------------------------------------------------------
/openhands/runtime/plugins/vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "workbench.colorTheme": "Default Dark Modern",
3 | "workbench.startupEditor": "none"
4 | }
5 |
--------------------------------------------------------------------------------
/openhands/runtime/utils/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.runtime.utils.system import (
2 | display_number_matrix,
3 | find_available_tcp_port,
4 | )
5 |
6 | __all__ = ['display_number_matrix', 'find_available_tcp_port']
7 |
--------------------------------------------------------------------------------
/openhands/runtime/utils/singleton.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/All-Hands-AI/OpenHands/4221ce856626199b409715f7f5d4752111a9fa25/openhands/runtime/utils/singleton.py
--------------------------------------------------------------------------------
/openhands/runtime/utils/tenacity_stop.py:
--------------------------------------------------------------------------------
1 | from tenacity import RetryCallState
2 | from tenacity.stop import stop_base
3 |
4 | from openhands.utils.shutdown_listener import should_exit
5 |
6 |
7 | class stop_if_should_exit(stop_base):
8 | """Stop if the should_exit flag is set."""
9 |
10 | def __call__(self, retry_state: 'RetryCallState') -> bool:
11 | return should_exit()
12 |
--------------------------------------------------------------------------------
/openhands/runtime/utils/vscode-extensions/hello-world/extension.js:
--------------------------------------------------------------------------------
1 | const vscode = require('vscode');
2 |
3 | function activate(context) {
4 | let disposable = vscode.commands.registerCommand('openhands-hello-world.helloWorld', function () {
5 | vscode.window.showInformationMessage('Hello from OpenHands!');
6 | });
7 |
8 | context.subscriptions.push(disposable);
9 | }
10 |
11 | function deactivate() {}
12 |
13 | module.exports = {
14 | activate,
15 | deactivate
16 | }
17 |
--------------------------------------------------------------------------------
/openhands/runtime/utils/vscode-extensions/hello-world/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "openhands-hello-world",
3 | "displayName": "OpenHands Hello World",
4 | "description": "A simple hello world extension for OpenHands",
5 | "version": "0.0.1",
6 | "publisher": "openhands",
7 | "engines": {
8 | "vscode": "^1.98.2"
9 | },
10 | "categories": [
11 | "Other"
12 | ],
13 | "activationEvents": [
14 | "onCommand:openhands-hello-world.helloWorld"
15 | ],
16 | "main": "./extension.js",
17 | "contributes": {
18 | "commands": [{
19 | "command": "openhands-hello-world.helloWorld",
20 | "title": "Hello World from OpenHands"
21 | }]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/openhands/security/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.security.analyzer import SecurityAnalyzer
2 | from openhands.security.invariant.analyzer import InvariantAnalyzer
3 |
4 | __all__ = [
5 | 'SecurityAnalyzer',
6 | 'InvariantAnalyzer',
7 | ]
8 |
--------------------------------------------------------------------------------
/openhands/security/invariant/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.security.invariant.analyzer import InvariantAnalyzer
2 |
3 | __all__ = [
4 | 'InvariantAnalyzer',
5 | ]
6 |
--------------------------------------------------------------------------------
/openhands/security/options.py:
--------------------------------------------------------------------------------
1 | from openhands.security.analyzer import SecurityAnalyzer
2 | from openhands.security.invariant.analyzer import InvariantAnalyzer
3 |
4 | SecurityAnalyzers: dict[str, type[SecurityAnalyzer]] = {
5 | 'invariant': InvariantAnalyzer,
6 | }
7 |
--------------------------------------------------------------------------------
/openhands/server/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/All-Hands-AI/OpenHands/4221ce856626199b409715f7f5d4752111a9fa25/openhands/server/__init__.py
--------------------------------------------------------------------------------
/openhands/server/__main__.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import uvicorn
4 |
5 |
6 | def main():
7 | uvicorn.run(
8 | 'openhands.server.listen:app',
9 | host='0.0.0.0',
10 | port=int(os.environ.get('port') or '3000'),
11 | log_level='debug' if os.environ.get('DEBUG') else 'info',
12 | )
13 |
14 |
15 | if __name__ == '__main__':
16 | main()
17 |
--------------------------------------------------------------------------------
/openhands/server/data_models/agent_loop_info.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass, field
2 |
3 | from openhands.events.event_store_abc import EventStoreABC
4 | from openhands.storage.data_models.conversation_status import ConversationStatus
5 |
6 |
7 | @dataclass
8 | class AgentLoopInfo:
9 | """
10 | Information about an agent loop - the URL on which to locate it and the event store
11 | """
12 |
13 | conversation_id: str
14 | url: str | None
15 | session_api_key: str | None
16 | event_store: EventStoreABC | None
17 | status: ConversationStatus = field(default=ConversationStatus.RUNNING)
18 |
--------------------------------------------------------------------------------
/openhands/server/data_models/conversation_info_result_set.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass, field
2 |
3 | from openhands.server.data_models.conversation_info import ConversationInfo
4 |
5 |
6 | @dataclass
7 | class ConversationInfoResultSet:
8 | results: list[ConversationInfo] = field(default_factory=list)
9 | next_page_id: str | None = None
10 |
--------------------------------------------------------------------------------
/openhands/server/mock/README.md:
--------------------------------------------------------------------------------
1 | # OpenHands mock server
2 | This is a simple mock server to facilitate development in the frontend.
3 |
4 | ## Start the Server
5 | Follow the instructions in the README to install dependencies. Then run:
6 | ```
7 | python listen.py
8 | ```
9 |
10 | Then open the frontend to connect to the mock server. It will simply reply to every received message.
11 |
--------------------------------------------------------------------------------
/openhands/server/session/__init__.py:
--------------------------------------------------------------------------------
1 | from openhands.server.session.session import Session
2 |
3 | __all__ = ['Session']
4 |
--------------------------------------------------------------------------------
/openhands/server/static.py:
--------------------------------------------------------------------------------
1 | from fastapi.staticfiles import StaticFiles
2 | from starlette.responses import Response
3 | from starlette.types import Scope
4 |
5 |
6 | class SPAStaticFiles(StaticFiles):
7 | async def get_response(self, path: str, scope: Scope) -> Response:
8 | try:
9 | return await super().get_response(path, scope)
10 | except Exception:
11 | # FIXME: just making this HTTPException doesn't work for some reason
12 | return await super().get_response('index.html', scope)
13 |
--------------------------------------------------------------------------------
/openhands/storage/data_models/conversation_metadata_result_set.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass, field
2 |
3 | from openhands.storage.data_models.conversation_metadata import ConversationMetadata
4 |
5 |
6 | @dataclass
7 | class ConversationMetadataResultSet:
8 | results: list[ConversationMetadata] = field(default_factory=list)
9 | next_page_id: str | None = None
10 |
--------------------------------------------------------------------------------
/openhands/storage/data_models/conversation_status.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 |
4 | class ConversationStatus(Enum):
5 | STARTING = 'STARTING'
6 | RUNNING = 'RUNNING'
7 | STOPPED = 'STOPPED'
8 |
--------------------------------------------------------------------------------
/openhands/storage/files.py:
--------------------------------------------------------------------------------
1 | from abc import abstractmethod
2 |
3 |
4 | class FileStore:
5 | @abstractmethod
6 | def write(self, path: str, contents: str | bytes) -> None:
7 | pass
8 |
9 | @abstractmethod
10 | def read(self, path: str) -> str:
11 | pass
12 |
13 | @abstractmethod
14 | def list(self, path: str) -> list[str]:
15 | pass
16 |
17 | @abstractmethod
18 | def delete(self, path: str) -> None:
19 | pass
20 |
--------------------------------------------------------------------------------
/openhands/utils/tenacity_stop.py:
--------------------------------------------------------------------------------
1 | from tenacity import RetryCallState
2 | from tenacity.stop import stop_base
3 |
4 | from openhands.utils.shutdown_listener import should_exit
5 |
6 |
7 | class stop_if_should_exit(stop_base):
8 | """Stop if the should_exit flag is set."""
9 |
10 | def __call__(self, retry_state: 'RetryCallState') -> bool:
11 | return bool(should_exit())
12 |
--------------------------------------------------------------------------------
/openhands/utils/term_color.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 | from termcolor import colored
4 |
5 |
6 | class TermColor(Enum):
7 | """Terminal color codes."""
8 |
9 | WARNING = 'yellow'
10 | SUCCESS = 'green'
11 | ERROR = 'red'
12 | INFO = 'blue'
13 |
14 |
15 | def colorize(text: str, color: TermColor = TermColor.WARNING) -> str:
16 | """Colorize text with specified color.
17 |
18 | Args:
19 | text (str): Text to be colored
20 | color (TermColor, optional): Color to use. Defaults to TermColor.WARNING
21 |
22 | Returns:
23 | str: Colored text
24 | """
25 | return colored(text, color.value)
26 |
--------------------------------------------------------------------------------
/pydoc-markdown.yml:
--------------------------------------------------------------------------------
1 | loaders:
2 | - type: python
3 | search_path: [.]
4 | processors:
5 | - type: filter
6 | skip_empty_modules: true
7 | - type: smart
8 | - type: crossref
9 | renderer:
10 | type: docusaurus
11 | docs_base_path: docs/modules
12 | relative_output_path: python
13 | relative_sidebar_path: sidebar.json
14 | sidebar_top_level_label: Backend
15 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | addopts = -p no:warnings
3 | asyncio_default_fixture_loop_scope = function
4 |
--------------------------------------------------------------------------------
/tests/unit/resolver/mock_output/repo/src/App.tsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import './App.css'
4 | import PullRequestViewer from './PullRequestViewer'
5 |
6 | function App() {
7 | return (
8 |