mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 05:37:20 +08:00
fix(frontend): Agent Tools & Metadata not available for V1 conversations (#12180)
Co-authored-by: amanape <83104063+amanape@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { adaptSystemMessage } from "#/utils/system-message-adapter";
|
||||
import { EventState } from "#/stores/use-event-store";
|
||||
import { SystemMessageModal } from "#/components/features/conversation-panel/system-message-modal";
|
||||
import { ToolsContextMenu } from "#/components/features/controls/tools-context-menu";
|
||||
|
||||
const v1Event: EventState["events"] = [
|
||||
{
|
||||
id: "v1-id",
|
||||
timestamp: "2025-12-30T12:00:00Z",
|
||||
source: "agent",
|
||||
system_prompt: {
|
||||
type: "text",
|
||||
text: "v1 prompt",
|
||||
},
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "bash",
|
||||
description: "Execute bash",
|
||||
parameters: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const adaptedResult = adaptSystemMessage(v1Event);
|
||||
|
||||
vi.mock("#/hooks/query/use-active-conversation", () => ({
|
||||
useActiveConversation: () => ({ data: { conversation_version: "V1" } }),
|
||||
}));
|
||||
|
||||
vi.mock("#/hooks/use-user-providers", () => ({
|
||||
useUserProviders: () => ({ providers: ["test"] }),
|
||||
}));
|
||||
|
||||
describe("SystemMessage UI Rendering", () => {
|
||||
it("should render the 'Show Agent Tools' button in the context menu", () => {
|
||||
render(
|
||||
<ToolsContextMenu
|
||||
onClose={() => {}}
|
||||
onShowSkills={() => {}}
|
||||
onShowAgentTools={() => {}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("show-agent-tools-button")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display the adapted v1 system prompt content correctly", () => {
|
||||
render(
|
||||
<SystemMessageModal
|
||||
isOpen
|
||||
onClose={() => {}}
|
||||
systemMessage={adaptedResult}
|
||||
/>,
|
||||
);
|
||||
|
||||
const messageElement = screen.getByText("v1 prompt");
|
||||
|
||||
expect(messageElement).toBeDefined();
|
||||
expect(messageElement).toBeVisible();
|
||||
});
|
||||
});
|
||||
37
frontend/__tests__/utils/system-message-adapter.test.ts
Normal file
37
frontend/__tests__/utils/system-message-adapter.test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { adaptSystemMessage } from "#/utils/system-message-adapter";
|
||||
import { EventState } from "#/stores/use-event-store";
|
||||
|
||||
const v1Event: EventState["events"] = [
|
||||
{
|
||||
id: "v1-id",
|
||||
timestamp: "2025-12-30T12:00:00Z",
|
||||
source: "agent",
|
||||
system_prompt: {
|
||||
type: "text",
|
||||
text: "v1 prompt",
|
||||
},
|
||||
tools: [
|
||||
{
|
||||
type: "function",
|
||||
function: {
|
||||
name: "bash",
|
||||
description: "Execute bash",
|
||||
parameters: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
describe("adaptSystemMessage", () => {
|
||||
it("should correctly adapt the v1 system_prompt event structure", () => {
|
||||
const result = adaptSystemMessage(v1Event);
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.content).toBe("v1 prompt");
|
||||
});
|
||||
|
||||
it("should return null when no system message is present in events", () => {
|
||||
expect(adaptSystemMessage([])).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -61,7 +61,7 @@ export function Tools() {
|
||||
<SystemMessageModal
|
||||
isOpen={systemModalVisible}
|
||||
onClose={() => setSystemModalVisible(false)}
|
||||
systemMessage={systemMessage ? systemMessage.args : null}
|
||||
systemMessage={systemMessage || null}
|
||||
/>
|
||||
|
||||
{/* Skills Modal */}
|
||||
|
||||
@@ -4,16 +4,12 @@ import { ModalBody } from "#/components/shared/modals/modal-body";
|
||||
import { SystemMessageHeader } from "./system-message-modal/system-message-header";
|
||||
import { TabNavigation } from "./system-message-modal/tab-navigation";
|
||||
import { TabContent } from "./system-message-modal/tab-content";
|
||||
import { SystemMessageForModal } from "#/utils/system-message-adapter";
|
||||
|
||||
interface SystemMessageModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
systemMessage: {
|
||||
content: string;
|
||||
tools: Array<Record<string, unknown>> | null;
|
||||
openhands_version: string | null;
|
||||
agent_class: string | null;
|
||||
} | null;
|
||||
systemMessage: SystemMessageForModal | null;
|
||||
}
|
||||
|
||||
export function SystemMessageModal({
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { SystemMessageContent } from "./system-message-content";
|
||||
import { ToolsList } from "./tools-list";
|
||||
import { EmptyToolsState } from "./empty-tools-state";
|
||||
import { ChatCompletionToolParam } from "#/types/v1/core";
|
||||
|
||||
interface TabContentProps {
|
||||
activeTab: "system" | "tools";
|
||||
systemMessage: {
|
||||
content: string;
|
||||
tools: Array<Record<string, unknown>> | null;
|
||||
tools: Array<Record<string, unknown>> | ChatCompletionToolParam[] | null;
|
||||
};
|
||||
expandedTools: Record<number, boolean>;
|
||||
onToggleTool: (index: number) => void;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Typography } from "#/ui/typography";
|
||||
import { ToolParameters } from "./tool-parameters";
|
||||
import { ToggleButton } from "./toggle-button";
|
||||
import { ChatCompletionToolParam } from "#/types/v1/core";
|
||||
|
||||
interface FunctionData {
|
||||
name?: string;
|
||||
@@ -17,7 +18,7 @@ interface ToolData {
|
||||
}
|
||||
|
||||
interface ToolItemProps {
|
||||
tool: Record<string, unknown>;
|
||||
tool: Record<string, unknown> | ChatCompletionToolParam;
|
||||
index: number;
|
||||
isExpanded: boolean;
|
||||
onToggle: (index: number) => void;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ChatCompletionToolParam } from "#/types/v1/core";
|
||||
import { ToolItem } from "./tool-item";
|
||||
|
||||
interface ToolsListProps {
|
||||
tools: Array<Record<string, unknown>>;
|
||||
tools: Array<Record<string, unknown>> | ChatCompletionToolParam[];
|
||||
expandedTools: Record<number, boolean>;
|
||||
onToggleTool: (index: number) => void;
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ export function ConversationName() {
|
||||
<SystemMessageModal
|
||||
isOpen={systemModalVisible}
|
||||
onClose={() => setSystemModalVisible(false)}
|
||||
systemMessage={systemMessage ? systemMessage.args : null}
|
||||
systemMessage={systemMessage || null}
|
||||
/>
|
||||
|
||||
{/* Skills Modal */}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { usePostHog } from "posthog-js/react";
|
||||
import { useParams, useNavigate } from "react-router";
|
||||
import { transformVSCodeUrl } from "#/utils/vscode-url-helper";
|
||||
import useMetricsStore from "#/stores/metrics-store";
|
||||
import { isSystemMessage, isActionOrObservation } from "#/types/core/guards";
|
||||
import { ConversationStatus } from "#/types/conversation-status";
|
||||
import ConversationService from "#/api/conversation-service/conversation-service.api";
|
||||
import { useDeleteConversation } from "./mutation/use-delete-conversation";
|
||||
@@ -18,9 +17,13 @@ import {
|
||||
} from "#/utils/custom-toast-handlers";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { useEventStore } from "#/stores/use-event-store";
|
||||
import { isV0Event } from "#/types/v1/type-guards";
|
||||
|
||||
import { useActiveConversation } from "./query/use-active-conversation";
|
||||
import { useDownloadConversation } from "./use-download-conversation";
|
||||
import {
|
||||
adaptSystemMessage,
|
||||
SystemMessageForModal,
|
||||
} from "#/utils/system-message-adapter";
|
||||
|
||||
interface UseConversationNameContextMenuProps {
|
||||
conversationId?: string;
|
||||
@@ -56,10 +59,8 @@ export function useConversationNameContextMenu({
|
||||
React.useState(false);
|
||||
const { mutateAsync: downloadConversation } = useDownloadConversation();
|
||||
|
||||
const systemMessage = events
|
||||
.filter(isV0Event)
|
||||
.filter(isActionOrObservation)
|
||||
.find(isSystemMessage);
|
||||
const systemMessage: SystemMessageForModal | null =
|
||||
adaptSystemMessage(events);
|
||||
|
||||
const handleDelete = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -5,14 +5,14 @@ import { OpenHandsParsedEvent } from "#/types/core";
|
||||
import { isV1Event } from "#/types/v1/type-guards";
|
||||
|
||||
// While we transition to v1 events, our store can handle both v0 and v1 events
|
||||
type OHEvent = (OpenHandsEvent | OpenHandsParsedEvent) & {
|
||||
export type OHEvent = (OpenHandsEvent | OpenHandsParsedEvent) & {
|
||||
isFromPlanningAgent?: boolean;
|
||||
};
|
||||
|
||||
const getEventId = (event: OHEvent): string | number | undefined =>
|
||||
"id" in event ? event.id : undefined;
|
||||
|
||||
interface EventState {
|
||||
export interface EventState {
|
||||
events: OHEvent[];
|
||||
eventIds: Set<string | number>;
|
||||
uiEvents: OHEvent[];
|
||||
|
||||
47
frontend/src/utils/system-message-adapter.ts
Normal file
47
frontend/src/utils/system-message-adapter.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { OHEvent } from "#/stores/use-event-store";
|
||||
import { isActionOrObservation, isSystemMessage } from "#/types/core/guards";
|
||||
import { ChatCompletionToolParam } from "#/types/v1/core";
|
||||
import {
|
||||
isSystemPromptEvent,
|
||||
isV0Event,
|
||||
isV1Event,
|
||||
} from "#/types/v1/type-guards";
|
||||
|
||||
export interface SystemMessageForModal {
|
||||
content: string;
|
||||
tools: ChatCompletionToolParam[] | Record<string, unknown>[] | null;
|
||||
openhands_version: string | null;
|
||||
agent_class: string | null;
|
||||
}
|
||||
|
||||
export function adaptSystemMessage(
|
||||
events: OHEvent[],
|
||||
): SystemMessageForModal | null {
|
||||
let systemMessage: SystemMessageForModal | null = null;
|
||||
const v0SystemMessage = events
|
||||
.filter(isV0Event)
|
||||
.filter(isActionOrObservation)
|
||||
.find(isSystemMessage);
|
||||
|
||||
// V1 System Prompt Event
|
||||
const v1SystemPromptEvent = events
|
||||
.filter(isV1Event)
|
||||
.find(isSystemPromptEvent);
|
||||
|
||||
if (v0SystemMessage) {
|
||||
systemMessage = v0SystemMessage.args;
|
||||
} else if (v1SystemPromptEvent) {
|
||||
systemMessage = {
|
||||
content: v1SystemPromptEvent.system_prompt.text,
|
||||
tools: v1SystemPromptEvent.tools ?? null,
|
||||
openhands_version: null,
|
||||
agent_class: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (systemMessage) {
|
||||
return systemMessage;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user