Add agent_class to SystemMessageAction and display in frontend (#7935)

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Xingyao Wang 2025-04-21 14:08:15 -04:00 committed by GitHub
parent 5b5adc5c7b
commit 0412949018
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 603 additions and 24 deletions

View File

@ -45,7 +45,15 @@ describe("Empty state", () => {
it("should render suggestions if empty", () => {
const { store } = renderWithProviders(<ChatInterface />, {
preloadedState: {
chat: { messages: [] },
chat: {
messages: [],
systemMessage: {
content: "",
tools: [],
openhands_version: null,
agent_class: null
}
},
},
});
@ -68,7 +76,15 @@ describe("Empty state", () => {
it("should render the default suggestions", () => {
renderWithProviders(<ChatInterface />, {
preloadedState: {
chat: { messages: [] },
chat: {
messages: [],
systemMessage: {
content: "",
tools: [],
openhands_version: null,
agent_class: null
}
},
},
});
@ -98,7 +114,15 @@ describe("Empty state", () => {
const user = userEvent.setup();
const { store } = renderWithProviders(<ChatInterface />, {
preloadedState: {
chat: { messages: [] },
chat: {
messages: [],
systemMessage: {
content: "",
tools: [],
openhands_version: null,
agent_class: null
}
},
},
});
@ -127,7 +151,15 @@ describe("Empty state", () => {
const user = userEvent.setup();
const { rerender } = renderWithProviders(<ChatInterface />, {
preloadedState: {
chat: { messages: [] },
chat: {
messages: [],
systemMessage: {
content: "",
tools: [],
openhands_version: null,
agent_class: null
}
},
},
});

View File

@ -9,6 +9,7 @@
"version": "0.33.0",
"dependencies": {
"@heroui/react": "2.7.6",
"@microlink/react-json-view": "^1.26.1",
"@monaco-editor/react": "^4.7.0-rc.0",
"@react-router/node": "^7.5.1",
"@react-router/serve": "^7.5.1",
@ -29,6 +30,7 @@
"i18next-http-backend": "^3.0.2",
"isbot": "^5.1.25",
"jose": "^6.0.10",
"lucide-react": "^0.501.0",
"monaco-editor": "^0.52.2",
"posthog-js": "^1.236.2",
"react": "^19.1.0",
@ -3588,6 +3590,24 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@microlink/react-json-view": {
"version": "1.26.1",
"resolved": "https://registry.npmjs.org/@microlink/react-json-view/-/react-json-view-1.26.1.tgz",
"integrity": "sha512-2H5QCYdZlJi+oN4YBiUYPPFTNh/KLCN9i9yz8NwmSkRqXSRXYtEVIRffc9L34jdopKGK/tK21SeuzXVJHQLkfQ==",
"license": "MIT",
"dependencies": {
"react-base16-styling": "~0.9.0",
"react-lifecycles-compat": "~3.0.4",
"react-textarea-autosize": "~8.5.7"
},
"engines": {
"node": ">=17"
},
"peerDependencies": {
"react": ">= 15",
"react-dom": ">= 15"
}
},
"node_modules/@mjackson/node-fetch-server": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@mjackson/node-fetch-server/-/node-fetch-server-0.2.0.tgz",
@ -6736,6 +6756,12 @@
"@babel/types": "^7.20.7"
}
},
"node_modules/@types/base16": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/base16/-/base16-1.0.5.tgz",
"integrity": "sha512-OzOWrTluG9cwqidEzC/Q6FAmIPcnZfm8BFRlIx0+UIUqnuAmi5OS88O0RpT3Yz6qdmqObvUhasrbNsCofE4W9A==",
"license": "MIT"
},
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
@ -7248,9 +7274,9 @@
"license": "ISC"
},
"node_modules/@vitejs/plugin-react": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.0.tgz",
"integrity": "sha512-x/EztcTKVj+TDeANY1WjNeYsvZjZdfWRMP/KXi5Yn8BoTzpa13ZltaQqKfvWYbX8CE10GOHHdC5v86jY9x8i/g==",
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz",
"integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.26.10",
@ -7935,6 +7961,12 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/base16": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz",
"integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==",
"license": "MIT"
},
"node_modules/basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
@ -9120,9 +9152,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.138",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.138.tgz",
"integrity": "sha512-FWlQc52z1dXqm+9cCJ2uyFgJkESd+16j6dBEjsgDNuHjBpuIzL8/lRc0uvh1k8RNI6waGo6tcy2DvwkTBJOLDg==",
"version": "1.5.139",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.139.tgz",
"integrity": "sha512-GGnRYOTdN5LYpwbIr0rwP/ZHOQSvAF6TG0LSzp28uCBb9JiXHJGmaaKw29qjNJc5bGnnp6kXJqRnGMQoELwi5w==",
"license": "ISC"
},
"node_modules/emoji-regex": {
@ -12502,6 +12534,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/lodash.curry": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz",
"integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==",
"license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
@ -12716,6 +12754,15 @@
"yallist": "^3.0.2"
}
},
"node_modules/lucide-react": {
"version": "0.501.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.501.0.tgz",
"integrity": "sha512-E2KoyhW59fCb/yUbR3rbDer83fqn7a8NG91ZhIot2yWaPHjPyGzzsNKh40N//GezYShAuycf3TcQksRQznIsRw==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/lz-string": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
@ -13885,9 +13932,9 @@
"license": "MIT"
},
"node_modules/msw": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/msw/-/msw-2.7.4.tgz",
"integrity": "sha512-A2kuMopOjAjNEYkn0AnB1uj+x7oBjLIunFk7Ud4icEnVWFf6iBekn8oXW4zIwcpfEdWP9sLqyVaHVzneWoGEww==",
"version": "2.7.5",
"resolved": "https://registry.npmjs.org/msw/-/msw-2.7.5.tgz",
"integrity": "sha512-00MyTlY3TJutBa5kiU+jWiz2z5pNJDYHn2TgPkGkh92kMmNH43RqvMXd8y/7HxNn8RjzUbvZWYZjcS36fdb6sw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@ -15149,6 +15196,46 @@
"node": ">=0.10.0"
}
},
"node_modules/react-base16-styling": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.9.1.tgz",
"integrity": "sha512-1s0CY1zRBOQ5M3T61wetEpvQmsYSNtWEcdYzyZNxKa8t7oDvaOn9d21xrGezGAHFWLM7SHcktPuPTrvoqxSfKw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.16.7",
"@types/base16": "^1.0.2",
"@types/lodash": "^4.14.178",
"base16": "^1.0.0",
"color": "^3.2.1",
"csstype": "^3.0.10",
"lodash.curry": "^4.1.1"
}
},
"node_modules/react-base16-styling/node_modules/color": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
"integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.3",
"color-string": "^1.6.0"
}
},
"node_modules/react-base16-styling/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/react-base16-styling/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT"
},
"node_modules/react-dom": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
@ -15225,6 +15312,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
"license": "MIT"
},
"node_modules/react-markdown": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
@ -17154,9 +17247,9 @@
}
},
"node_modules/tinyglobby/node_modules/fdir": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
"integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"license": "MIT",
"peerDependencies": {
"picomatch": "^3 || ^4"
@ -17981,9 +18074,9 @@
}
},
"node_modules/vite/node_modules/fdir": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
"integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"license": "MIT",
"peerDependencies": {
"picomatch": "^3 || ^4"

View File

@ -8,6 +8,7 @@
},
"dependencies": {
"@heroui/react": "2.7.6",
"@microlink/react-json-view": "^1.26.1",
"@monaco-editor/react": "^4.7.0-rc.0",
"@react-router/node": "^7.5.1",
"@react-router/serve": "^7.5.1",
@ -28,6 +29,7 @@
"i18next-http-backend": "^3.0.2",
"isbot": "^5.1.25",
"jose": "^6.0.10",
"lucide-react": "^0.501.0",
"monaco-editor": "^0.52.2",
"posthog-js": "^1.236.2",
"react": "^19.1.0",

View File

@ -8,7 +8,7 @@
* - Please do NOT serve this file on production.
*/
const PACKAGE_VERSION = '2.7.3'
const PACKAGE_VERSION = '2.7.5'
const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()

View File

@ -8,6 +8,7 @@ interface ConversationCardContextMenuProps {
onDelete?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onEdit?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onDisplayCost?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onShowAgentTools?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onDownloadViaVSCode?: (event: React.MouseEvent<HTMLButtonElement>) => void;
position?: "top" | "bottom";
}
@ -17,6 +18,7 @@ export function ConversationCardContextMenu({
onDelete,
onEdit,
onDisplayCost,
onShowAgentTools,
onDownloadViaVSCode,
position = "bottom",
}: ConversationCardContextMenuProps) {
@ -58,6 +60,14 @@ export function ConversationCardContextMenu({
Display Cost
</ContextMenuListItem>
)}
{onShowAgentTools && (
<ContextMenuListItem
testId="show-agent-tools-button"
onClick={onShowAgentTools}
>
Show Agent Tools & Metadata
</ContextMenuListItem>
)}
</ContextMenu>
);
}

View File

@ -10,10 +10,12 @@ import {
} from "./conversation-state-indicator";
import { EllipsisButton } from "./ellipsis-button";
import { ConversationCardContextMenu } from "./conversation-card-context-menu";
import { SystemMessageModal } from "./system-message-modal";
import { cn } from "#/utils/utils";
import { BaseModal } from "../../shared/modals/base-modal/base-modal";
import { RootState } from "#/store";
import { I18nKey } from "#/i18n/declaration";
import { selectSystemMessage } from "#/state/chat-slice";
interface ConversationCardProps {
onClick?: () => void;
@ -52,10 +54,12 @@ export function ConversationCard({
const [contextMenuVisible, setContextMenuVisible] = React.useState(false);
const [titleMode, setTitleMode] = React.useState<"view" | "edit">("view");
const [metricsModalVisible, setMetricsModalVisible] = React.useState(false);
const [systemModalVisible, setSystemModalVisible] = React.useState(false);
const inputRef = React.useRef<HTMLInputElement>(null);
// Subscribe to metrics data from Redux store
const metrics = useSelector((state: RootState) => state.metrics);
const systemMessage = useSelector(selectSystemMessage);
const handleBlur = () => {
if (inputRef.current?.value) {
@ -129,6 +133,11 @@ export function ConversationCard({
setMetricsModalVisible(true);
};
const handleShowAgentTools = (event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
setSystemModalVisible(true);
};
React.useEffect(() => {
if (titleMode === "edit") {
inputRef.current?.focus();
@ -207,6 +216,11 @@ export function ConversationCard({
: undefined
}
onDisplayCost={showOptions ? handleDisplayCost : undefined}
onShowAgentTools={
showOptions && systemMessage
? handleShowAgentTools
: undefined
}
position={variant === "compact" ? "top" : "bottom"}
/>
)}
@ -315,6 +329,12 @@ export function ConversationCard({
)}
</div>
</BaseModal>
<SystemMessageModal
isOpen={systemModalVisible}
onClose={() => setSystemModalVisible(false)}
systemMessage={systemMessage}
/>
</>
);
}

View File

@ -0,0 +1,238 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { ChevronDown, ChevronRight } from "lucide-react";
import ReactJsonView from "@microlink/react-json-view";
import { BaseModalTitle } from "#/components/shared/modals/confirmation-modals/base-modal";
import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
import { ModalBody } from "#/components/shared/modals/modal-body";
import { cn } from "#/utils/utils";
// Custom JSON viewer theme that matches our application theme
const jsonViewTheme = {
base00: "transparent", // background
base01: "#2d2d2d", // lighter background
base02: "#4e4e4e", // selection background
base03: "#6c6c6c", // comments, invisibles
base04: "#969896", // dark foreground
base05: "#d9d9d9", // default foreground
base06: "#e8e8e8", // light foreground
base07: "#ffffff", // light background
base08: "#ff5370", // variables, red
base09: "#f78c6c", // integers, orange
base0A: "#ffcb6b", // booleans, yellow
base0B: "#c3e88d", // strings, green
base0C: "#89ddff", // support, cyan
base0D: "#82aaff", // functions, blue
base0E: "#c792ea", // keywords, purple
base0F: "#ff5370", // deprecated, red
};
interface SystemMessageModalProps {
isOpen: boolean;
onClose: () => void;
systemMessage: {
content: string;
tools: Array<Record<string, unknown>> | null;
openhands_version: string | null;
agent_class: string | null;
} | null;
}
interface FunctionData {
name?: string;
description?: string;
parameters?: Record<string, unknown>;
}
interface ToolData {
type?: string;
function?: FunctionData;
name?: string;
description?: string;
parameters?: Record<string, unknown>;
}
export function SystemMessageModal({
isOpen,
onClose,
systemMessage,
}: SystemMessageModalProps) {
const { t } = useTranslation();
const [activeTab, setActiveTab] = useState<"system" | "tools">("system");
const [expandedTools, setExpandedTools] = useState<Record<number, boolean>>(
{},
);
if (!systemMessage) {
return null;
}
const toggleTool = (index: number) => {
setExpandedTools((prev) => ({
...prev,
[index]: !prev[index],
}));
};
return (
isOpen && (
<ModalBackdrop onClose={onClose}>
<ModalBody
width="medium"
className="max-h-[80vh] flex flex-col items-start"
>
<div className="flex flex-col gap-6 w-full">
<BaseModalTitle title={t("SYSTEM_MESSAGE_MODAL$TITLE")} />
<div className="flex flex-col gap-2">
{systemMessage.agent_class && (
<div className="text-sm">
<span className="font-semibold text-gray-300">
{t("SYSTEM_MESSAGE_MODAL$AGENT_CLASS")}
</span>{" "}
<span className="font-medium text-gray-100">
{systemMessage.agent_class}
</span>
</div>
)}
{systemMessage.openhands_version && (
<div className="text-sm">
<span className="font-semibold text-gray-300">
{t("SYSTEM_MESSAGE_MODAL$OPENHANDS_VERSION")}
</span>{" "}
<span className="text-gray-100">
{systemMessage.openhands_version}
</span>
</div>
)}
</div>
</div>
<div className="w-full">
<div className="flex border-b mb-2">
<button
type="button"
className={cn(
"px-4 py-2 font-medium border-b-2 transition-colors",
activeTab === "system"
? "border-primary text-gray-100"
: "border-transparent hover:text-gray-700 dark:hover:text-gray-300",
)}
onClick={() => setActiveTab("system")}
>
{t("SYSTEM_MESSAGE_MODAL$SYSTEM_MESSAGE_TAB")}
</button>
{systemMessage.tools && systemMessage.tools.length > 0 && (
<button
type="button"
className={cn(
"px-4 py-2 font-medium border-b-2 transition-colors",
activeTab === "tools"
? "border-primary text-gray-100"
: "border-transparent hover:text-gray-700 dark:hover:text-gray-300",
)}
onClick={() => setActiveTab("tools")}
>
{t("SYSTEM_MESSAGE_MODAL$TOOLS_TAB")}
</button>
)}
</div>
<div className="h-[60vh] overflow-auto rounded-md">
{activeTab === "system" && (
<div className="p-4 whitespace-pre-wrap font-mono text-sm leading-relaxed text-gray-300 shadow-inner">
{systemMessage.content}
</div>
)}
{activeTab === "tools" &&
systemMessage.tools &&
systemMessage.tools.length > 0 && (
<div className="p-2 space-y-3">
{systemMessage.tools.map((tool, index) => {
// Extract function data from the nested structure
const toolData = tool as ToolData;
const functionData = toolData.function || toolData;
const name =
functionData.name ||
(toolData.type === "function" &&
toolData.function?.name) ||
"";
const description =
functionData.description ||
(toolData.type === "function" &&
toolData.function?.description) ||
"";
const parameters =
functionData.parameters ||
(toolData.type === "function" &&
toolData.function?.parameters) ||
null;
const isExpanded = expandedTools[index] || false;
return (
<div key={index} className="rounded-md overflow-hidden">
<button
type="button"
onClick={() => toggleTool(index)}
className="w-full py-3 px-2 text-left flex items-center justify-between hover:bg-gray-700 transition-colors"
>
<div className="flex items-center">
<h3 className="font-bold text-gray-100">
{String(name)}
</h3>
</div>
<span className="text-gray-300">
{isExpanded ? (
<ChevronDown size={18} />
) : (
<ChevronRight size={18} />
)}
</span>
</button>
{isExpanded && (
<div className="px-2 pb-3 pt-1">
<div className="mt-2 mb-3">
<p className="text-sm whitespace-pre-wrap text-gray-300 leading-relaxed">
{String(description)}
</p>
</div>
{/* Parameters section */}
{parameters && (
<div className="mt-2">
<h4 className="text-sm font-semibold text-gray-300">
{t("SYSTEM_MESSAGE_MODAL$PARAMETERS")}
</h4>
<div className="text-sm mt-2 p-3 bg-gray-900 rounded-md overflow-auto text-gray-300 max-h-[400px] shadow-inner">
<ReactJsonView
src={parameters}
theme={jsonViewTheme}
/>
</div>
</div>
)}
</div>
)}
</div>
);
})}
</div>
)}
{activeTab === "tools" &&
(!systemMessage.tools || systemMessage.tools.length === 0) && (
<div className="flex items-center justify-center h-full p-4">
<p className="text-gray-400">
{t("SYSTEM_MESSAGE_MODAL$NO_TOOLS")}
</p>
</div>
)}
</div>
</div>
</ModalBody>
</ModalBackdrop>
)
);
}

View File

@ -1,18 +1,28 @@
import React from "react";
import { cn } from "#/utils/utils";
type ModalWidth = "small" | "medium";
interface ModalBodyProps {
testID?: string;
children: React.ReactNode;
className?: React.HTMLProps<HTMLDivElement>["className"];
width?: ModalWidth;
}
export function ModalBody({ testID, children, className }: ModalBodyProps) {
export function ModalBody({
testID,
children,
className,
width = "small",
}: ModalBodyProps) {
return (
<div
data-testid={testID}
className={cn(
"bg-base-secondary flex flex-col gap-6 items-center w-[384px] p-6 rounded-xl",
"bg-base-secondary flex flex-col gap-6 items-center p-6 rounded-xl",
width === "small" && "w-[384px]",
width === "medium" && "w-[700px]",
className,
)}
>

View File

@ -326,6 +326,7 @@ export enum I18nKey {
ACTION_MESSAGE$BROWSE = "ACTION_MESSAGE$BROWSE",
ACTION_MESSAGE$BROWSE_INTERACTIVE = "ACTION_MESSAGE$BROWSE_INTERACTIVE",
ACTION_MESSAGE$THINK = "ACTION_MESSAGE$THINK",
ACTION_MESSAGE$SYSTEM = "ACTION_MESSAGE$SYSTEM",
OBSERVATION_MESSAGE$RUN = "OBSERVATION_MESSAGE$RUN",
OBSERVATION_MESSAGE$RUN_IPYTHON = "OBSERVATION_MESSAGE$RUN_IPYTHON",
OBSERVATION_MESSAGE$READ = "OBSERVATION_MESSAGE$READ",
@ -416,4 +417,11 @@ export enum I18nKey {
DIFF_VIEWER$ASK_OH = "DIFF_VIEWER$ASK_OH",
DIFF_VIEWER$NO_CHANGES = "DIFF_VIEWER$NO_CHANGES",
DIFF_VIEWER$WAITING_FOR_RUNTIME = "DIFF_VIEWER$WAITING_FOR_RUNTIME",
SYSTEM_MESSAGE_MODAL$TITLE = "SYSTEM_MESSAGE_MODAL$TITLE",
SYSTEM_MESSAGE_MODAL$AGENT_CLASS = "SYSTEM_MESSAGE_MODAL$AGENT_CLASS",
SYSTEM_MESSAGE_MODAL$OPENHANDS_VERSION = "SYSTEM_MESSAGE_MODAL$OPENHANDS_VERSION",
SYSTEM_MESSAGE_MODAL$SYSTEM_MESSAGE_TAB = "SYSTEM_MESSAGE_MODAL$SYSTEM_MESSAGE_TAB",
SYSTEM_MESSAGE_MODAL$TOOLS_TAB = "SYSTEM_MESSAGE_MODAL$TOOLS_TAB",
SYSTEM_MESSAGE_MODAL$PARAMETERS = "SYSTEM_MESSAGE_MODAL$PARAMETERS",
SYSTEM_MESSAGE_MODAL$NO_TOOLS = "SYSTEM_MESSAGE_MODAL$NO_TOOLS",
}

View File

@ -4869,6 +4869,21 @@
"es": "Pensando",
"tr": "Düşünüyor"
},
"ACTION_MESSAGE$SYSTEM": {
"en": "System Message",
"zh-CN": "系统消息",
"zh-TW": "系統訊息",
"ko-KR": "시스템 메시지",
"ja": "システムメッセージ",
"no": "Systemmelding",
"ar": "رسالة النظام",
"de": "Systemnachricht",
"fr": "Message Système",
"it": "Messaggio di Sistema",
"pt": "Mensagem do Sistema",
"es": "Mensaje del Sistema",
"tr": "Sistem Mesajı"
},
"OBSERVATION_MESSAGE$RUN": {
"en": "Ran <cmd>{{observation.payload.extras.command}}</cmd>",
"zh-CN": "运行 <cmd>{{observation.payload.extras.command}}</cmd>",
@ -6207,5 +6222,110 @@
"fr": "En attente du démarrage de l'exécution...",
"tr": "Çalışma zamanının başlamasını bekliyor...",
"de": "Warten auf den Start der Laufzeit..."
},
"SYSTEM_MESSAGE_MODAL$TITLE": {
"en": "Agent Tools & Metadata",
"zh-CN": "代理工具和元数据",
"zh-TW": "代理工具和元數據",
"ko-KR": "에이전트 도구 및 메타데이터",
"ja": "エージェントツールとメタデータ",
"no": "Agent-verktøy og metadata",
"ar": "أدوات الوكيل والبيانات الوصفية",
"de": "Agent-Tools und Metadaten",
"fr": "Outils d'agent et métadonnées",
"it": "Strumenti e metadati dell'agente",
"pt": "Ferramentas e metadados do agente",
"es": "Herramientas y metadatos del agente",
"tr": "Ajan Araçları ve Meta Verileri"
},
"SYSTEM_MESSAGE_MODAL$AGENT_CLASS": {
"en": "Agent Class:",
"zh-CN": "代理类别:",
"zh-TW": "代理類別:",
"ko-KR": "에이전트 클래스:",
"ja": "エージェントクラス:",
"no": "Agent-klasse:",
"ar": "فئة الوكيل:",
"de": "Agent-Klasse:",
"fr": "Classe d'agent :",
"it": "Classe agente:",
"pt": "Classe do agente:",
"es": "Clase de agente:",
"tr": "Ajan Sınıfı:"
},
"SYSTEM_MESSAGE_MODAL$OPENHANDS_VERSION": {
"en": "OpenHands Version:",
"zh-CN": "OpenHands 版本:",
"zh-TW": "OpenHands 版本:",
"ko-KR": "OpenHands 버전:",
"ja": "OpenHands バージョン:",
"no": "OpenHands-versjon:",
"ar": "إصدار OpenHands:",
"de": "OpenHands-Version:",
"fr": "Version OpenHands :",
"it": "Versione OpenHands:",
"pt": "Versão OpenHands:",
"es": "Versión de OpenHands:",
"tr": "OpenHands Sürümü:"
},
"SYSTEM_MESSAGE_MODAL$SYSTEM_MESSAGE_TAB": {
"en": "System Message",
"zh-CN": "系统消息",
"zh-TW": "系統訊息",
"ko-KR": "시스템 메시지",
"ja": "システムメッセージ",
"no": "Systemmelding",
"ar": "رسالة النظام",
"de": "Systemnachricht",
"fr": "Message système",
"it": "Messaggio di sistema",
"pt": "Mensagem do sistema",
"es": "Mensaje del sistema",
"tr": "Sistem Mesajı"
},
"SYSTEM_MESSAGE_MODAL$TOOLS_TAB": {
"en": "Available Tools",
"zh-CN": "可用工具",
"zh-TW": "可用工具",
"ko-KR": "사용 가능한 도구",
"ja": "利用可能なツール",
"no": "Tilgjengelige verktøy",
"ar": "الأدوات المتاحة",
"de": "Verfügbare Tools",
"fr": "Outils disponibles",
"it": "Strumenti disponibili",
"pt": "Ferramentas disponíveis",
"es": "Herramientas disponibles",
"tr": "Kullanılabilir Araçlar"
},
"SYSTEM_MESSAGE_MODAL$PARAMETERS": {
"en": "Parameters:",
"zh-CN": "参数:",
"zh-TW": "參數:",
"ko-KR": "매개변수:",
"ja": "パラメータ:",
"no": "Parametere:",
"ar": "المعلمات:",
"de": "Parameter:",
"fr": "Paramètres :",
"it": "Parametri:",
"pt": "Parâmetros:",
"es": "Parámetros:",
"tr": "Parametreler:"
},
"SYSTEM_MESSAGE_MODAL$NO_TOOLS": {
"en": "No tools available for this agent",
"zh-CN": "此代理没有可用的工具",
"zh-TW": "此代理沒有可用的工具",
"ko-KR": "이 에이전트에 사용 가능한 도구가 없습니다",
"ja": "このエージェントで利用可能なツールはありません",
"no": "Ingen verktøy tilgjengelig for denne agenten",
"ar": "لا توجد أدوات متاحة لهذا الوكيل",
"de": "Keine Tools für diesen Agenten verfügbar",
"fr": "Aucun outil disponible pour cet agent",
"it": "Nessuno strumento disponibile per questo agente",
"pt": "Nenhuma ferramenta disponível para este agente",
"es": "No hay herramientas disponibles para este agente",
"tr": "Bu ajan için kullanılabilir araç yok"
}
}

View File

@ -11,7 +11,15 @@ import {
RecallObservation,
} from "#/types/core/observations";
type SliceState = { messages: Message[] };
type SliceState = {
messages: Message[];
systemMessage: {
content: string;
tools: Array<Record<string, unknown>> | null;
openhands_version: string | null;
agent_class: string | null;
} | null;
};
const MAX_CONTENT_LENGTH = 1000;
@ -25,6 +33,7 @@ const HANDLED_ACTIONS: OpenHandsEventType[] = [
"edit",
"recall",
"think",
"system",
];
function getRiskText(risk: ActionSecurityRisk) {
@ -43,6 +52,7 @@ function getRiskText(risk: ActionSecurityRisk) {
const initialState: SliceState = {
messages: [],
systemMessage: null,
};
export const chatSlice = createSlice({
@ -100,6 +110,18 @@ export const chatSlice = createSlice({
}
const translationID = `ACTION_MESSAGE$${actionID.toUpperCase()}`;
let text = "";
if (actionID === "system") {
// Store the system message in the state
state.systemMessage = {
content: action.payload.args.content,
tools: action.payload.args.tools,
openhands_version: action.payload.args.openhands_version,
agent_class: action.payload.args.agent_class,
};
// Don't add a message for system actions
return;
}
if (actionID === "run") {
text = `Command:\n\`${action.payload.args.command}\``;
} else if (actionID === "run_ipython") {
@ -295,6 +317,7 @@ export const chatSlice = createSlice({
clearMessages(state: SliceState) {
state.messages = [];
state.systemMessage = null;
},
},
});
@ -307,4 +330,9 @@ export const {
addErrorMessage,
clearMessages,
} = chatSlice.actions;
// Selectors
export const selectSystemMessage = (state: { chat: SliceState }) =>
state.chat.systemMessage;
export default chatSlice.reducer;

View File

@ -5,6 +5,9 @@ enum ActionType {
// Represents a message from the user or agent.
MESSAGE = "message",
// Represents a system message for an agent, including the system prompt and available tools.
SYSTEM = "system",
// Reads the contents of a file.
READ = "read",

View File

@ -9,6 +9,16 @@ export interface UserMessageAction extends OpenHandsActionEvent<"message"> {
};
}
export interface SystemMessageAction extends OpenHandsActionEvent<"system"> {
source: "agent";
args: {
content: string;
tools: Array<Record<string, unknown>> | null;
openhands_version: string | null;
agent_class: string | null;
};
}
export interface CommandAction extends OpenHandsActionEvent<"run"> {
source: "agent";
args: {
@ -145,6 +155,7 @@ export interface RecallAction extends OpenHandsActionEvent<"recall"> {
export type OpenHandsAction =
| UserMessageAction
| AssistantMessageAction
| SystemMessageAction
| CommandAction
| IPythonAction
| ThinkAction

View File

@ -1,5 +1,6 @@
export type OpenHandsEventType =
| "message"
| "system"
| "agent_state_changed"
| "run"
| "read"

View File

@ -68,7 +68,7 @@ class Agent(ABC):
tools = getattr(self, 'tools', None)
system_message_action = SystemMessageAction(
content=system_message, tools=tools
content=system_message, tools=tools, agent_class=self.name
)
# Set the source attribute
system_message_action._source = EventSource.AGENT # type: ignore

View File

@ -46,6 +46,7 @@ class SystemMessageAction(Action):
content: str
tools: list[Any] | None = None
openhands_version: str | None = openhands.__version__
agent_class: str | None = None
action: ActionType = ActionType.SYSTEM
@property
@ -57,4 +58,6 @@ class SystemMessageAction(Action):
ret += f'CONTENT: {self.content}'
if self.tools:
ret += f'\nTOOLS: {len(self.tools)} tools available'
if self.agent_class:
ret += f'\nAGENT_CLASS: {self.agent_class}'
return ret