diff --git a/frontend/__tests__/components/chat/chat-interface.test.tsx b/frontend/__tests__/components/chat/chat-interface.test.tsx index b010ab2dc3..69c67406b6 100644 --- a/frontend/__tests__/components/chat/chat-interface.test.tsx +++ b/frontend/__tests__/components/chat/chat-interface.test.tsx @@ -45,7 +45,15 @@ describe("Empty state", () => { it("should render suggestions if empty", () => { const { store } = renderWithProviders(, { 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(, { 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(, { 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(, { preloadedState: { - chat: { messages: [] }, + chat: { + messages: [], + systemMessage: { + content: "", + tools: [], + openhands_version: null, + agent_class: null + } + }, }, }); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6d80af70c0..31d435dc24 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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" diff --git a/frontend/package.json b/frontend/package.json index 976d819c1e..cac7315c04 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/public/mockServiceWorker.js b/frontend/public/mockServiceWorker.js index 34057e898f..8b841baf2a 100644 --- a/frontend/public/mockServiceWorker.js +++ b/frontend/public/mockServiceWorker.js @@ -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() diff --git a/frontend/src/components/features/conversation-panel/conversation-card-context-menu.tsx b/frontend/src/components/features/conversation-panel/conversation-card-context-menu.tsx index 743d745716..0ee1cd425c 100644 --- a/frontend/src/components/features/conversation-panel/conversation-card-context-menu.tsx +++ b/frontend/src/components/features/conversation-panel/conversation-card-context-menu.tsx @@ -8,6 +8,7 @@ interface ConversationCardContextMenuProps { onDelete?: (event: React.MouseEvent) => void; onEdit?: (event: React.MouseEvent) => void; onDisplayCost?: (event: React.MouseEvent) => void; + onShowAgentTools?: (event: React.MouseEvent) => void; onDownloadViaVSCode?: (event: React.MouseEvent) => 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 )} + {onShowAgentTools && ( + + Show Agent Tools & Metadata + + )} ); } diff --git a/frontend/src/components/features/conversation-panel/conversation-card.tsx b/frontend/src/components/features/conversation-panel/conversation-card.tsx index 52370acd04..6ca9c801ea 100644 --- a/frontend/src/components/features/conversation-panel/conversation-card.tsx +++ b/frontend/src/components/features/conversation-panel/conversation-card.tsx @@ -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(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) => { + 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({ )} + + setSystemModalVisible(false)} + systemMessage={systemMessage} + /> ); } diff --git a/frontend/src/components/features/conversation-panel/system-message-modal.tsx b/frontend/src/components/features/conversation-panel/system-message-modal.tsx new file mode 100644 index 0000000000..414c1d9080 --- /dev/null +++ b/frontend/src/components/features/conversation-panel/system-message-modal.tsx @@ -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> | null; + openhands_version: string | null; + agent_class: string | null; + } | null; +} + +interface FunctionData { + name?: string; + description?: string; + parameters?: Record; +} + +interface ToolData { + type?: string; + function?: FunctionData; + name?: string; + description?: string; + parameters?: Record; +} + +export function SystemMessageModal({ + isOpen, + onClose, + systemMessage, +}: SystemMessageModalProps) { + const { t } = useTranslation(); + const [activeTab, setActiveTab] = useState<"system" | "tools">("system"); + const [expandedTools, setExpandedTools] = useState>( + {}, + ); + + if (!systemMessage) { + return null; + } + + const toggleTool = (index: number) => { + setExpandedTools((prev) => ({ + ...prev, + [index]: !prev[index], + })); + }; + + return ( + isOpen && ( + + +
+ +
+ {systemMessage.agent_class && ( +
+ + {t("SYSTEM_MESSAGE_MODAL$AGENT_CLASS")} + {" "} + + {systemMessage.agent_class} + +
+ )} + {systemMessage.openhands_version && ( +
+ + {t("SYSTEM_MESSAGE_MODAL$OPENHANDS_VERSION")} + {" "} + + {systemMessage.openhands_version} + +
+ )} +
+
+ +
+
+ + {systemMessage.tools && systemMessage.tools.length > 0 && ( + + )} +
+ +
+ {activeTab === "system" && ( +
+ {systemMessage.content} +
+ )} + + {activeTab === "tools" && + systemMessage.tools && + systemMessage.tools.length > 0 && ( +
+ {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 ( +
+ + + {isExpanded && ( +
+
+

+ {String(description)} +

+
+ + {/* Parameters section */} + {parameters && ( +
+

+ {t("SYSTEM_MESSAGE_MODAL$PARAMETERS")} +

+
+ +
+
+ )} +
+ )} +
+ ); + })} +
+ )} + + {activeTab === "tools" && + (!systemMessage.tools || systemMessage.tools.length === 0) && ( +
+

+ {t("SYSTEM_MESSAGE_MODAL$NO_TOOLS")} +

+
+ )} +
+
+
+
+ ) + ); +} diff --git a/frontend/src/components/shared/modals/modal-body.tsx b/frontend/src/components/shared/modals/modal-body.tsx index 5d7b059f72..3f503527af 100644 --- a/frontend/src/components/shared/modals/modal-body.tsx +++ b/frontend/src/components/shared/modals/modal-body.tsx @@ -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["className"]; + width?: ModalWidth; } -export function ModalBody({ testID, children, className }: ModalBodyProps) { +export function ModalBody({ + testID, + children, + className, + width = "small", +}: ModalBodyProps) { return (
diff --git a/frontend/src/i18n/declaration.ts b/frontend/src/i18n/declaration.ts index 1cf81f641f..9183f6f5f4 100644 --- a/frontend/src/i18n/declaration.ts +++ b/frontend/src/i18n/declaration.ts @@ -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", } diff --git a/frontend/src/i18n/translation.json b/frontend/src/i18n/translation.json index 6acf018c17..ef6dedb1b1 100644 --- a/frontend/src/i18n/translation.json +++ b/frontend/src/i18n/translation.json @@ -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 {{observation.payload.extras.command}}", "zh-CN": "运行 {{observation.payload.extras.command}}", @@ -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" } } diff --git a/frontend/src/state/chat-slice.ts b/frontend/src/state/chat-slice.ts index 0d70005ec0..e6b2790f4e 100644 --- a/frontend/src/state/chat-slice.ts +++ b/frontend/src/state/chat-slice.ts @@ -11,7 +11,15 @@ import { RecallObservation, } from "#/types/core/observations"; -type SliceState = { messages: Message[] }; +type SliceState = { + messages: Message[]; + systemMessage: { + content: string; + tools: Array> | 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; diff --git a/frontend/src/types/action-type.tsx b/frontend/src/types/action-type.tsx index 315abcc7b0..f623233ba5 100644 --- a/frontend/src/types/action-type.tsx +++ b/frontend/src/types/action-type.tsx @@ -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", diff --git a/frontend/src/types/core/actions.ts b/frontend/src/types/core/actions.ts index 6a8bb61792..5010eb3822 100644 --- a/frontend/src/types/core/actions.ts +++ b/frontend/src/types/core/actions.ts @@ -9,6 +9,16 @@ export interface UserMessageAction extends OpenHandsActionEvent<"message"> { }; } +export interface SystemMessageAction extends OpenHandsActionEvent<"system"> { + source: "agent"; + args: { + content: string; + tools: Array> | 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 diff --git a/frontend/src/types/core/base.ts b/frontend/src/types/core/base.ts index c52824661f..cb3b27baa9 100644 --- a/frontend/src/types/core/base.ts +++ b/frontend/src/types/core/base.ts @@ -1,5 +1,6 @@ export type OpenHandsEventType = | "message" + | "system" | "agent_state_changed" | "run" | "read" diff --git a/openhands/controller/agent.py b/openhands/controller/agent.py index 78bdbb126d..034cacf481 100644 --- a/openhands/controller/agent.py +++ b/openhands/controller/agent.py @@ -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 diff --git a/openhands/events/action/message.py b/openhands/events/action/message.py index 511df18aec..1e8eb7568a 100644 --- a/openhands/events/action/message.py +++ b/openhands/events/action/message.py @@ -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