Skip to content

Commit

Permalink
Merge branch 'main' into fix-httpx-example
Browse files Browse the repository at this point in the history
  • Loading branch information
tofarr authored Jan 22, 2025
2 parents 759d42e + 04e36df commit 5c644ce
Show file tree
Hide file tree
Showing 37 changed files with 258 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ Les options de configuration de l'agent sont définies dans les sections `[agent
- Description : Si l'éditeur LLM est activé dans l'espace d'action (fonctionne uniquement avec l'appel de fonction)

**Utilisation du micro-agent**
- `use_microagents`
- `enable_prompt_extensions`
- Type : `bool`
- Valeur par défaut : `true`
- Description : Indique si l'utilisation des micro-agents est activée ou non
Expand Down
3 changes: 3 additions & 0 deletions evaluation/benchmarks/miniwob/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Please follow instruction [here](../../README.md#setup) to setup your local deve

## Test if your environment works

Follow the instructions here https://miniwob.farama.org/content/getting_started/ & https://miniwob.farama.org/content/viewing/
to set up MiniWoB server in your local environment at http://localhost:8080/miniwob/

Access with browser the above MiniWoB URLs and see if they load correctly.

## Run Evaluation
Expand Down
4 changes: 2 additions & 2 deletions evaluation/benchmarks/swe_bench/eval_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def process_git_patch(patch):
return patch


def get_config(instance: pd.Series) -> AppConfig:
def get_config(metadata: EvalMetadata, instance: pd.Series) -> AppConfig:
# We use a different instance image for the each instance of swe-bench eval
base_container_image = get_instance_docker_image(instance['instance_id'])
logger.info(
Expand Down Expand Up @@ -132,7 +132,7 @@ def process_instance(
else:
logger.info(f'Starting evaluation for instance {instance.instance_id}.')

config = get_config(instance)
config = get_config(metadata, instance)
instance_id = instance.instance_id
model_patch = instance['model_patch']
test_spec: TestSpec = instance['test_spec']
Expand Down
1 change: 1 addition & 0 deletions evaluation/benchmarks/swe_bench/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ def get_config(
codeact_enable_browsing=RUN_WITH_BROWSING,
codeact_enable_llm_editor=False,
condenser=metadata.condenser_config,
enable_prompt_extensions=False,
)
config.set_agent_config(agent_config)
return config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ describe("Sidebar", () => {
await user.click(advancedOptionsSwitch);

const apiKeyInput = within(settingsModal).getByLabelText(/API\$KEY/i);
await user.type(apiKeyInput, "SET");
await user.type(apiKeyInput, "**********");

const saveButton = within(settingsModal).getByTestId(
"save-settings-button",
Expand Down
18 changes: 10 additions & 8 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"@playwright/test": "^1.49.1",
"@react-router/dev": "^7.1.2",
"@tailwindcss/typography": "^0.5.16",
"@tanstack/eslint-plugin-query": "^5.62.16",
"@tanstack/eslint-plugin-query": "^5.64.2",
"@testing-library/jest-dom": "^6.6.1",
"@testing-library/react": "^16.2.0",
"@testing-library/user-event": "^14.6.0",
Expand All @@ -100,7 +100,7 @@
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prettier": "^5.2.2",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^4.6.2",
"husky": "^9.1.6",
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/api/open-hands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
AuthenticateResponse,
Conversation,
ResultSet,
GetTrajectoryResponse,
} from "./open-hands.types";
import { openHands } from "./open-hands-axios";
import { ApiSettings } from "#/services/settings";
Expand Down Expand Up @@ -354,6 +355,15 @@ class OpenHands {

return response.data.items;
}

static async getTrajectory(
conversationId: string,
): Promise<GetTrajectoryResponse> {
const { data } = await openHands.get<GetTrajectoryResponse>(
`/api/conversations/${conversationId}/trajectory`,
);
return data;
}
}

export default OpenHands;
5 changes: 5 additions & 0 deletions frontend/src/api/open-hands.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ export interface GetVSCodeUrlResponse {
error?: string;
}

export interface GetTrajectoryResponse {
trajectory: unknown[] | null;
error?: string;
}

export interface AuthenticateResponse {
message?: string;
error?: string;
Expand Down
29 changes: 29 additions & 0 deletions frontend/src/components/features/chat/chat-interface.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { useDispatch, useSelector } from "react-redux";
import toast from "react-hot-toast";
import React from "react";
import posthog from "posthog-js";
import { useParams } from "react-router";
import { convertImageToBase64 } from "#/utils/convert-image-to-base-64";
import { FeedbackActions } from "../feedback/feedback-actions";
import { ExportActions } from "../export/export-actions";
import { createChatMessage } from "#/services/chat-service";
import { InteractiveChatBox } from "./interactive-chat-box";
import { addUserMessage } from "#/state/chat-slice";
Expand All @@ -19,6 +22,8 @@ import { ActionSuggestions } from "./action-suggestions";
import { ContinueButton } from "#/components/shared/buttons/continue-button";
import { ScrollToBottomButton } from "#/components/shared/buttons/scroll-to-bottom-button";
import { LoadingSpinner } from "#/components/shared/loading-spinner";
import { useGetTrajectory } from "#/hooks/mutation/use-get-trajectory";
import { downloadTrajectory } from "#/utils/download-files";

function getEntryPoint(
hasRepository: boolean | null,
Expand Down Expand Up @@ -47,6 +52,8 @@ export function ChatInterface() {
const { selectedRepository, importedProjectZip } = useSelector(
(state: RootState) => state.initialQuery,
);
const params = useParams();
const { mutate: getTrajectory } = useGetTrajectory();

const handleSendMessage = async (content: string, files: File[]) => {
if (messages.length === 0) {
Expand Down Expand Up @@ -90,6 +97,25 @@ export function ChatInterface() {
setFeedbackPolarity(polarity);
};

const onClickExportTrajectoryButton = () => {
if (!params.conversationId) {
toast.error("ConversationId unknown, cannot download trajectory");
return;
}

getTrajectory(params.conversationId, {
onSuccess: async (data) => {
await downloadTrajectory(
params.conversationId ?? "unknown",
data.trajectory,
);
},
onError: (error) => {
toast.error(error.message);
},
});
};

const isWaitingForUserInput =
curAgentState === AgentState.AWAITING_USER_INPUT ||
curAgentState === AgentState.FINISHED;
Expand Down Expand Up @@ -137,6 +163,9 @@ export function ChatInterface() {
onClickShareFeedbackActionButton("negative")
}
/>
<ExportActions
onExportTrajectory={() => onClickExportTrajectoryButton()}
/>

<div className="absolute left-1/2 transform -translate-x-1/2 bottom-0">
{messages.length > 2 &&
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/components/features/export/export-actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import ExportIcon from "#/icons/export.svg?react";
import { ExportActionButton } from "#/components/shared/buttons/export-action-button";

interface ExportActionsProps {
onExportTrajectory: () => void;
}

export function ExportActions({ onExportTrajectory }: ExportActionsProps) {
return (
<div data-testid="export-actions" className="flex gap-1">
<ExportActionButton
onClick={onExportTrajectory}
icon={<ExportIcon width={15} height={15} />}
/>
</div>
);
}
17 changes: 17 additions & 0 deletions frontend/src/components/shared/buttons/export-action-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
interface ExportActionButtonProps {
onClick: () => void;
icon: React.ReactNode;
}

export function ExportActionButton({ onClick, icon }: ExportActionButtonProps) {
return (
<button
type="button"
onClick={onClick}
className="button-base p-1 hover:bg-neutral-500"
title="Export trajectory"
>
{icon}
</button>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export function SettingsForm({

<APIKeyInput
isDisabled={!!disabled}
isSet={settings.LLM_API_KEY === "SET"}
isSet={settings.LLM_API_KEY === "**********"}
/>

{showAdvancedOptions && (
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/context/settings-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function SettingsProvider({ children }: SettingsProviderProps) {
...newSettings,
};

if (updatedSettings.LLM_API_KEY === "SET") {
if (updatedSettings.LLM_API_KEY === "**********") {
delete updatedSettings.LLM_API_KEY;
}

Expand Down
7 changes: 7 additions & 0 deletions frontend/src/hooks/mutation/use-get-trajectory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useMutation } from "@tanstack/react-query";
import OpenHands from "#/api/open-hands";

export const useGetTrajectory = () =>
useMutation({
mutationFn: (cid: string) => OpenHands.getTrajectory(cid),
});
5 changes: 5 additions & 0 deletions frontend/src/icons/export.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions frontend/src/types/file-system.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ interface FileSystemDirectoryHandle {
): Promise<FileSystemFileHandle>;
}

interface SaveFilePickerOptions {
suggestedName?: string;
types?: Array<{
description?: string;
accept: Record<string, string[]>;
}>;
excludeAcceptAllOption?: boolean;
}

interface Window {
showDirectoryPicker(): Promise<FileSystemDirectoryHandle>;
showSaveFilePicker(
options?: SaveFilePickerOptions,
): Promise<FileSystemFileHandle>;
}
40 changes: 40 additions & 0 deletions frontend/src/utils/download-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ function isFileSystemAccessSupported(): boolean {
return "showDirectoryPicker" in window;
}

/**
* Checks if the Save File Picker API is supported
*/
function isSaveFilePickerSupported(): boolean {
return "showSaveFilePicker" in window;
}

/**
* Creates subdirectories and returns the final directory handle
*/
Expand Down Expand Up @@ -162,6 +169,39 @@ async function processBatch(
};
}

export async function downloadTrajectory(
conversationId: string,
data: unknown[] | null,
): Promise<void> {
try {
if (!isSaveFilePickerSupported()) {
throw new Error(
"Your browser doesn't support downloading folders. Please use Chrome, Edge, or another browser that supports the File System Access API.",
);
}
const options = {
suggestedName: `trajectory-${conversationId}.json`,
types: [
{
description: "JSON File",
accept: {
"application/json": [".json"],
},
},
],
};

const handle = await window.showSaveFilePicker(options);
const writable = await handle.createWritable();
await writable.write(JSON.stringify(data, null, 2));
await writable.close();
} catch (error) {
throw new Error(
`Failed to download file: ${error instanceof Error ? error.message : String(error)}`,
);
}
}

/**
* Downloads files from the workspace one by one
* @param initialPath Initial path to start downloading from. If not provided, downloads from root
Expand Down
9 changes: 2 additions & 7 deletions openhands/agenthub/codeact_agent/codeact_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,16 +277,11 @@ def get_observation_message(
# if it doesn't have tool call metadata, it was triggered by a user action
if obs.tool_call_metadata is None:
text = truncate_content(
f'\nObserved result of command executed by user:\n{obs.content}',
f'\nObserved result of command executed by user:\n{obs.to_agent_observation()}',
max_message_chars,
)
else:
text = truncate_content(
obs.content
+ f'\n[Python Interpreter: {obs.metadata.py_interpreter_path}]',
max_message_chars,
)
text += f'\n[Command finished with exit code {obs.exit_code}]'
text = truncate_content(obs.to_agent_observation(), max_message_chars)
message = Message(role='user', content=[TextContent(text=text)])
elif isinstance(obs, IPythonRunCellObservation):
text = obs.content
Expand Down
4 changes: 2 additions & 2 deletions openhands/agenthub/codeact_agent/function_calling.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
),
)

_FILE_EDIT_DESCRIPTION = """Edit a file.
_FILE_EDIT_DESCRIPTION = """Edit a file in plain-text format.
* The assistant can edit files by specifying the file path and providing a draft of the new file content.
* The draft content doesn't need to be exactly the same as the existing file; the assistant may skip unchanged lines using comments like `# unchanged` to indicate unchanged sections.
* IMPORTANT: For large files (e.g., > 300 lines), specify the range of lines to edit using `start` and `end` (1-indexed, inclusive). The range should be smaller than 300 lines.
Expand Down Expand Up @@ -216,7 +216,7 @@ def __init__(self):
),
)

_STR_REPLACE_EDITOR_DESCRIPTION = """Custom editing tool for viewing, creating and editing files
_STR_REPLACE_EDITOR_DESCRIPTION = """Custom editing tool for viewing, creating and editing files in plain-text format
* State is persistent across command calls and discussions with the user
* If `path` is a file, `view` displays the result of applying `cat -n`. If `path` is a directory, `view` lists non-hidden files and directories up to 2 levels deep
* The `create` command cannot be used if the specified `path` already exists as a file
Expand Down
Loading

0 comments on commit 5c644ce

Please sign in to comment.