mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d57bb264de | |||
| 5df917008b | |||
| 3de9f38451 | |||
| 135d3aea09 | |||
| 68ddda7e2e | |||
| 5e7ea6da6d | |||
| 67d7b00620 | |||
| 4edd575f93 | |||
| d72f31a3b0 |
+1
-1
@@ -159,7 +159,7 @@ poetry run pytest ./tests/unit/test_*.py
|
||||
To reduce build time (e.g., if no changes were made to the client-runtime component), you can use an existing Docker
|
||||
container image by setting the SANDBOX_RUNTIME_CONTAINER_IMAGE environment variable to the desired Docker image.
|
||||
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.56-nikolaik`
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.57-nikolaik`
|
||||
|
||||
## Develop inside Docker container
|
||||
|
||||
|
||||
@@ -79,17 +79,17 @@ You'll find OpenHands running at [http://localhost:3000](http://localhost:3000)
|
||||
You can also run OpenHands directly with Docker:
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.56-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.57-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.56-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.57-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.56
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.57
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
+3
-3
@@ -51,17 +51,17 @@ OpenHands也可以使用Docker在本地系统上运行。
|
||||
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.56-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.57-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.56-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.57-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.56
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.57
|
||||
```
|
||||
|
||||
> **注意**: 如果您在0.44版本之前使用过OpenHands,您可能需要运行 `mv ~/.openhands-state ~/.openhands` 来将对话历史迁移到新位置。
|
||||
|
||||
+3
-3
@@ -42,17 +42,17 @@ OpenHandsはDockerを利用してローカル環境でも実行できます。
|
||||
> 公共ネットワークで実行していますか?[Hardened Docker Installation Guide](https://docs.all-hands.dev/usage/runtimes/docker#hardened-docker-installation)を参照して、ネットワークバインディングの制限や追加のセキュリティ対策を実施してください。
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.56-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.57-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.56-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.57-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.56
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.57
|
||||
```
|
||||
|
||||
**注**: バージョン0.44以前のOpenHandsを使用していた場合は、会話履歴を移行するために `mv ~/.openhands-state ~/.openhands` を実行してください。
|
||||
|
||||
@@ -12,7 +12,7 @@ services:
|
||||
- SANDBOX_API_HOSTNAME=host.docker.internal
|
||||
- DOCKER_HOST_ADDR=host.docker.internal
|
||||
#
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.56-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.57-nikolaik}
|
||||
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ services:
|
||||
image: openhands:latest
|
||||
container_name: openhands-app-${DATE:-}
|
||||
environment:
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.56-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.57-nikolaik}
|
||||
#- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234} # enable this only if you want a specific non-root sandbox user but you will have to manually adjust permissions of ~/.openhands for this user
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
@@ -113,7 +113,7 @@ The conversation history will be saved in `~/.openhands/sessions`.
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.56-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.57-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -122,7 +122,7 @@ docker run -it \
|
||||
-v ~/.openhands:/.openhands \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.56 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.57 \
|
||||
python -m openhands.cli.entry --override-cli-mode true
|
||||
```
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export GITHUB_TOKEN="your-token" # Required for repository operations
|
||||
# Run OpenHands
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.56-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.57-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -73,7 +73,7 @@ docker run -it \
|
||||
-v ~/.openhands:/.openhands \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.56 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.57 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi"
|
||||
```
|
||||
|
||||
|
||||
@@ -68,23 +68,23 @@ Download and install the LM Studio desktop app from [lmstudio.ai](https://lmstud
|
||||
1. Check [the installation guide](/usage/local-setup) and ensure all prerequisites are met before running OpenHands, then run:
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.56-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.57-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.56-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.57-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.56
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.57
|
||||
```
|
||||
|
||||
2. Wait until the server is running (see log below):
|
||||
```
|
||||
Digest: sha256:e72f9baecb458aedb9afc2cd5bc935118d1868719e55d50da73190d3a85c674f
|
||||
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.56
|
||||
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.57
|
||||
Starting OpenHands...
|
||||
Running OpenHands as root
|
||||
14:22:13 - openhands:INFO: server_config.py:50 - Using config class None
|
||||
|
||||
@@ -116,17 +116,17 @@ Note that you'll still need `uv` installed for the default MCP servers to work p
|
||||
<Accordion title="Docker Command (Click to expand)">
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.56-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.57-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.56-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.57-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.56
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.57
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -2,7 +2,6 @@ from experiments.constants import (
|
||||
ENABLE_EXPERIMENT_MANAGER,
|
||||
)
|
||||
from experiments.experiment_versions import (
|
||||
handle_claude4_vs_gpt5_experiment,
|
||||
handle_condenser_max_step_experiment,
|
||||
handle_system_prompt_experiment,
|
||||
)
|
||||
@@ -44,9 +43,6 @@ class SaaSExperimentManager(ExperimentManager):
|
||||
return conversation_settings
|
||||
|
||||
# Apply conversation-scoped experiments
|
||||
conversation_settings = handle_claude4_vs_gpt5_experiment(
|
||||
user_id, conversation_id, conversation_settings
|
||||
)
|
||||
conversation_settings = handle_condenser_max_step_experiment(
|
||||
user_id, conversation_id, conversation_settings
|
||||
)
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.56.0",
|
||||
"version": "0.57.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.56.0",
|
||||
"version": "0.57.0",
|
||||
"dependencies": {
|
||||
"@heroui/react": "^2.8.3",
|
||||
"@heroui/use-infinite-scroll": "^2.2.11",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.56.0",
|
||||
"version": "0.57.2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { Typography } from "#/ui/typography";
|
||||
import { Pre } from "#/ui/pre";
|
||||
|
||||
interface MicroagentContentProps {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export function MicroagentContent({ content }: MicroagentContentProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="mt-2">
|
||||
<Typography.Text className="text-sm font-semibold text-gray-300 mb-2">
|
||||
{t(I18nKey.MICROAGENTS_MODAL$CONTENT)}
|
||||
</Typography.Text>
|
||||
<Pre
|
||||
size="default"
|
||||
font="mono"
|
||||
lineHeight="relaxed"
|
||||
background="dark"
|
||||
textColor="light"
|
||||
padding="medium"
|
||||
borderRadius="medium"
|
||||
shadow="inner"
|
||||
maxHeight="small"
|
||||
overflow="auto"
|
||||
className="mt-2"
|
||||
>
|
||||
{content || t(I18nKey.MICROAGENTS_MODAL$NO_CONTENT)}
|
||||
</Pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { ChevronDown, ChevronRight } from "lucide-react";
|
||||
import { Microagent } from "#/api/open-hands.types";
|
||||
import { Typography } from "#/ui/typography";
|
||||
import { MicroagentTriggers } from "./microagent-triggers";
|
||||
import { MicroagentContent } from "./microagent-content";
|
||||
|
||||
interface MicroagentItemProps {
|
||||
agent: Microagent;
|
||||
isExpanded: boolean;
|
||||
onToggle: (agentName: string) => void;
|
||||
}
|
||||
|
||||
export function MicroagentItem({
|
||||
agent,
|
||||
isExpanded,
|
||||
onToggle,
|
||||
}: MicroagentItemProps) {
|
||||
return (
|
||||
<div className="rounded-md overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onToggle(agent.name)}
|
||||
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">
|
||||
<Typography.Text className="font-bold text-gray-100">
|
||||
{agent.name}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Typography.Text className="px-2 py-1 text-xs rounded-full bg-gray-800 mr-2">
|
||||
{agent.type === "repo" ? "Repository" : "Knowledge"}
|
||||
</Typography.Text>
|
||||
<Typography.Text className="text-gray-300">
|
||||
{isExpanded ? (
|
||||
<ChevronDown size={18} />
|
||||
) : (
|
||||
<ChevronRight size={18} />
|
||||
)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{isExpanded && (
|
||||
<div className="px-2 pb-3 pt-1">
|
||||
<MicroagentTriggers triggers={agent.triggers} />
|
||||
<MicroagentContent content={agent.content} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { Typography } from "#/ui/typography";
|
||||
|
||||
interface MicroagentTriggersProps {
|
||||
triggers: string[];
|
||||
}
|
||||
|
||||
export function MicroagentTriggers({ triggers }: MicroagentTriggersProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!triggers || triggers.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-2 mb-3">
|
||||
<Typography.Text className="text-sm font-semibold text-gray-300 mb-2">
|
||||
{t(I18nKey.MICROAGENTS_MODAL$TRIGGERS)}
|
||||
</Typography.Text>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{triggers.map((trigger) => (
|
||||
<Typography.Text
|
||||
key={trigger}
|
||||
className="px-2 py-1 text-xs rounded-full bg-blue-900"
|
||||
>
|
||||
{trigger}
|
||||
</Typography.Text>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { Typography } from "#/ui/typography";
|
||||
|
||||
interface MicroagentsEmptyStateProps {
|
||||
isError: boolean;
|
||||
}
|
||||
|
||||
export function MicroagentsEmptyState({ isError }: MicroagentsEmptyStateProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full p-4">
|
||||
<Typography.Text className="text-gray-400">
|
||||
{isError
|
||||
? t(I18nKey.MICROAGENTS_MODAL$FETCH_ERROR)
|
||||
: t(I18nKey.CONVERSATION$NO_MICROAGENTS)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
export function MicroagentsLoadingState() {
|
||||
return (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-primary" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { RefreshCw } from "lucide-react";
|
||||
import { BaseModalTitle } from "#/components/shared/modals/confirmation-modals/base-modal";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { BrandButton } from "../settings/brand-button";
|
||||
|
||||
interface MicroagentsModalHeaderProps {
|
||||
isAgentReady: boolean;
|
||||
isLoading: boolean;
|
||||
isRefetching: boolean;
|
||||
onRefresh: () => void;
|
||||
}
|
||||
|
||||
export function MicroagentsModalHeader({
|
||||
isAgentReady,
|
||||
isLoading,
|
||||
isRefetching,
|
||||
onRefresh,
|
||||
}: MicroagentsModalHeaderProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 w-full">
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<BaseModalTitle title={t(I18nKey.MICROAGENTS_MODAL$TITLE)} />
|
||||
{isAgentReady && (
|
||||
<BrandButton
|
||||
testId="refresh-microagents"
|
||||
type="button"
|
||||
variant="primary"
|
||||
className="flex items-center gap-2"
|
||||
onClick={onRefresh}
|
||||
isDisabled={isLoading || isRefetching}
|
||||
>
|
||||
<RefreshCw
|
||||
size={16}
|
||||
className={`${isRefetching ? "animate-spin" : ""}`}
|
||||
/>
|
||||
{t(I18nKey.BUTTON$REFRESH)}
|
||||
</BrandButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { ChevronDown, ChevronRight, RefreshCw } from "lucide-react";
|
||||
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 { I18nKey } from "#/i18n/declaration";
|
||||
import { useConversationMicroagents } from "#/hooks/query/use-conversation-microagents";
|
||||
import { RootState } from "#/store";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
import { Typography } from "#/ui/typography";
|
||||
import { MicroagentsModalHeader } from "./microagents-modal-header";
|
||||
import { MicroagentsLoadingState } from "./microagents-loading-state";
|
||||
import { MicroagentsEmptyState } from "./microagents-empty-state";
|
||||
import { MicroagentItem } from "./microagent-item";
|
||||
import { BrandButton } from "../settings/brand-button";
|
||||
|
||||
interface MicroagentsModalProps {
|
||||
onClose: () => void;
|
||||
@@ -49,34 +47,57 @@ export function MicroagentsModal({ onClose }: MicroagentsModalProps) {
|
||||
className="max-h-[80vh] flex flex-col items-start"
|
||||
testID="microagents-modal"
|
||||
>
|
||||
<MicroagentsModalHeader
|
||||
isAgentReady={isAgentReady}
|
||||
isLoading={isLoading}
|
||||
isRefetching={isRefetching}
|
||||
onRefresh={refetch}
|
||||
/>
|
||||
<div className="flex flex-col gap-6 w-full">
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<BaseModalTitle title={t(I18nKey.MICROAGENTS_MODAL$TITLE)} />
|
||||
{isAgentReady && (
|
||||
<BrandButton
|
||||
testId="refresh-microagents"
|
||||
type="button"
|
||||
variant="primary"
|
||||
className="flex items-center gap-2"
|
||||
onClick={refetch}
|
||||
isDisabled={isLoading || isRefetching}
|
||||
>
|
||||
<RefreshCw
|
||||
size={16}
|
||||
className={`${isRefetching ? "animate-spin" : ""}`}
|
||||
/>
|
||||
{t(I18nKey.BUTTON$REFRESH)}
|
||||
</BrandButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isAgentReady && (
|
||||
<Typography.Text className="text-sm text-gray-400">
|
||||
<span className="text-sm text-gray-400">
|
||||
{t(I18nKey.MICROAGENTS_MODAL$WARNING)}
|
||||
</Typography.Text>
|
||||
</span>
|
||||
)}
|
||||
|
||||
<div className="w-full h-[60vh] overflow-auto rounded-md custom-scrollbar-always">
|
||||
<div className="w-full h-[60vh] overflow-auto rounded-md">
|
||||
{!isAgentReady && (
|
||||
<div className="w-full h-full flex items-center text-center justify-center text-2xl text-tertiary-light">
|
||||
<Typography.Text>
|
||||
{t(I18nKey.DIFF_VIEWER$WAITING_FOR_RUNTIME)}
|
||||
</Typography.Text>
|
||||
{t(I18nKey.DIFF_VIEWER$WAITING_FOR_RUNTIME)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLoading && <MicroagentsLoadingState />}
|
||||
{isLoading && (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-primary" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading &&
|
||||
isAgentReady &&
|
||||
(isError || !microagents || microagents.length === 0) && (
|
||||
<MicroagentsEmptyState isError={isError} />
|
||||
<div className="flex items-center justify-center h-full p-4">
|
||||
<p className="text-gray-400">
|
||||
{isError
|
||||
? t(I18nKey.MICROAGENTS_MODAL$FETCH_ERROR)
|
||||
: t(I18nKey.CONVERSATION$NO_MICROAGENTS)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading &&
|
||||
@@ -88,12 +109,68 @@ export function MicroagentsModal({ onClose }: MicroagentsModalProps) {
|
||||
const isExpanded = expandedAgents[agent.name] || false;
|
||||
|
||||
return (
|
||||
<MicroagentItem
|
||||
<div
|
||||
key={agent.name}
|
||||
agent={agent}
|
||||
isExpanded={isExpanded}
|
||||
onToggle={toggleAgent}
|
||||
/>
|
||||
className="rounded-md overflow-hidden"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleAgent(agent.name)}
|
||||
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">
|
||||
{agent.name}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="px-2 py-1 text-xs rounded-full bg-gray-800 mr-2">
|
||||
{agent.type === "repo" ? "Repository" : "Knowledge"}
|
||||
</span>
|
||||
<span className="text-gray-300">
|
||||
{isExpanded ? (
|
||||
<ChevronDown size={18} />
|
||||
) : (
|
||||
<ChevronRight size={18} />
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{isExpanded && (
|
||||
<div className="px-2 pb-3 pt-1">
|
||||
{agent.triggers && agent.triggers.length > 0 && (
|
||||
<div className="mt-2 mb-3">
|
||||
<h4 className="text-sm font-semibold text-gray-300 mb-2">
|
||||
{t(I18nKey.MICROAGENTS_MODAL$TRIGGERS)}
|
||||
</h4>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{agent.triggers.map((trigger) => (
|
||||
<span
|
||||
key={trigger}
|
||||
className="px-2 py-1 text-xs rounded-full bg-blue-900"
|
||||
>
|
||||
{trigger}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-2">
|
||||
<h4 className="text-sm font-semibold text-gray-300 mb-2">
|
||||
{t(I18nKey.MICROAGENTS_MODAL$CONTENT)}
|
||||
</h4>
|
||||
<div className="text-sm mt-2 p-3 bg-gray-900 rounded-md overflow-auto text-gray-300 max-h-[400px] shadow-inner">
|
||||
<pre className="whitespace-pre-wrap font-mono text-sm leading-relaxed">
|
||||
{agent.content ||
|
||||
t(I18nKey.MICROAGENTS_MODAL$NO_CONTENT)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { useState } from "react";
|
||||
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 { 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 { cn } from "#/utils/utils";
|
||||
import { JSON_VIEW_THEME } from "#/utils/constants";
|
||||
|
||||
interface SystemMessageModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -16,11 +19,26 @@ interface SystemMessageModalProps {
|
||||
} | 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>>(
|
||||
{},
|
||||
@@ -44,27 +62,155 @@ export function SystemMessageModal({
|
||||
width="medium"
|
||||
className="max-h-[80vh] flex flex-col items-start"
|
||||
>
|
||||
<SystemMessageHeader
|
||||
agentClass={systemMessage.agent_class}
|
||||
openhandsVersion={systemMessage.openhands_version}
|
||||
/>
|
||||
<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">
|
||||
<TabNavigation
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
hasTools={
|
||||
!!(systemMessage.tools && systemMessage.tools.length > 0)
|
||||
}
|
||||
/>
|
||||
<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="max-h-[51vh] overflow-auto rounded-md custom-scrollbar-always">
|
||||
<TabContent
|
||||
activeTab={activeTab}
|
||||
systemMessage={systemMessage}
|
||||
expandedTools={expandedTools}
|
||||
onToggleTool={toggleTool}
|
||||
/>
|
||||
<div className="max-h-[51vh] 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
|
||||
name={false}
|
||||
src={parameters}
|
||||
theme={JSON_VIEW_THEME}
|
||||
/>
|
||||
</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>
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Typography } from "#/ui/typography";
|
||||
|
||||
export function EmptyToolsState() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full p-4">
|
||||
<Typography.Text className="text-gray-400">
|
||||
{t("SYSTEM_MESSAGE_MODAL$NO_TOOLS")}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
import { Typography } from "#/ui/typography";
|
||||
|
||||
interface SystemMessageContentProps {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export function SystemMessageContent({ content }: SystemMessageContentProps) {
|
||||
return (
|
||||
<div className="p-4 shadow-inner">
|
||||
<Typography.CodeBlock>{content}</Typography.CodeBlock>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BaseModalTitle } from "#/components/shared/modals/confirmation-modals/base-modal";
|
||||
import { Typography } from "#/ui/typography";
|
||||
|
||||
interface SystemMessageHeaderProps {
|
||||
agentClass: string | null;
|
||||
openhandsVersion: string | null;
|
||||
}
|
||||
|
||||
export function SystemMessageHeader({
|
||||
agentClass,
|
||||
openhandsVersion,
|
||||
}: SystemMessageHeaderProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 w-full">
|
||||
<BaseModalTitle title={t("SYSTEM_MESSAGE_MODAL$TITLE")} />
|
||||
<div className="flex flex-col gap-2">
|
||||
{agentClass && (
|
||||
<div className="text-sm">
|
||||
<Typography.Text className="font-semibold text-gray-300">
|
||||
{t("SYSTEM_MESSAGE_MODAL$AGENT_CLASS")}
|
||||
</Typography.Text>{" "}
|
||||
<Typography.Text className="font-medium text-gray-100">
|
||||
{agentClass}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
{openhandsVersion && (
|
||||
<div className="text-sm">
|
||||
<Typography.Text className="font-semibold text-gray-300">
|
||||
{t("SYSTEM_MESSAGE_MODAL$OPENHANDS_VERSION")}
|
||||
</Typography.Text>{" "}
|
||||
<Typography.Text className="text-gray-100">
|
||||
{openhandsVersion}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
import { cn } from "#/utils/utils";
|
||||
|
||||
interface TabButtonProps {
|
||||
isActive: boolean;
|
||||
children: React.ReactNode;
|
||||
onClick: () => void;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function TabButton({
|
||||
isActive,
|
||||
children,
|
||||
onClick,
|
||||
className,
|
||||
disabled = false,
|
||||
}: TabButtonProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"px-4 py-2 font-medium border-b-2 transition-colors",
|
||||
isActive
|
||||
? "border-primary text-gray-100"
|
||||
: "border-transparent hover:text-gray-700 dark:hover:text-gray-300",
|
||||
disabled && "opacity-50 cursor-not-allowed",
|
||||
className,
|
||||
)}
|
||||
onClick={onClick}
|
||||
aria-selected={isActive}
|
||||
role="tab"
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
import { SystemMessageContent } from "./system-message-content";
|
||||
import { ToolsList } from "./tools-list";
|
||||
import { EmptyToolsState } from "./empty-tools-state";
|
||||
|
||||
interface TabContentProps {
|
||||
activeTab: "system" | "tools";
|
||||
systemMessage: {
|
||||
content: string;
|
||||
tools: Array<Record<string, unknown>> | null;
|
||||
};
|
||||
expandedTools: Record<number, boolean>;
|
||||
onToggleTool: (index: number) => void;
|
||||
}
|
||||
|
||||
export function TabContent({
|
||||
activeTab,
|
||||
systemMessage,
|
||||
expandedTools,
|
||||
onToggleTool,
|
||||
}: TabContentProps) {
|
||||
if (activeTab === "system") {
|
||||
return <SystemMessageContent content={systemMessage.content} />;
|
||||
}
|
||||
|
||||
if (activeTab === "tools") {
|
||||
if (systemMessage.tools && systemMessage.tools.length > 0) {
|
||||
return (
|
||||
<ToolsList
|
||||
tools={systemMessage.tools}
|
||||
expandedTools={expandedTools}
|
||||
onToggleTool={onToggleTool}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <EmptyToolsState />;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TabButton } from "./tab-button";
|
||||
|
||||
interface TabNavigationProps {
|
||||
activeTab: "system" | "tools";
|
||||
onTabChange: (tab: "system" | "tools") => void;
|
||||
hasTools: boolean;
|
||||
}
|
||||
|
||||
export function TabNavigation({
|
||||
activeTab,
|
||||
onTabChange,
|
||||
hasTools,
|
||||
}: TabNavigationProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex border-b mb-2" role="tablist">
|
||||
<TabButton
|
||||
isActive={activeTab === "system"}
|
||||
onClick={() => onTabChange("system")}
|
||||
>
|
||||
{t("SYSTEM_MESSAGE_MODAL$SYSTEM_MESSAGE_TAB")}
|
||||
</TabButton>
|
||||
{hasTools && (
|
||||
<TabButton
|
||||
isActive={activeTab === "tools"}
|
||||
onClick={() => onTabChange("tools")}
|
||||
>
|
||||
{t("SYSTEM_MESSAGE_MODAL$TOOLS_TAB")}
|
||||
</TabButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
import { ChevronDown, ChevronRight } from "lucide-react";
|
||||
import { Typography } from "#/ui/typography";
|
||||
|
||||
interface ToggleButtonProps {
|
||||
title: string;
|
||||
isExpanded: boolean;
|
||||
onClick: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ToggleButton({
|
||||
title,
|
||||
isExpanded,
|
||||
onClick,
|
||||
className,
|
||||
}: ToggleButtonProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={`w-full py-3 px-2 text-left flex items-center justify-between hover:bg-gray-700 transition-colors ${className || ""}`}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Typography.Text className="font-bold text-gray-100">
|
||||
{title}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Typography.Text className="text-gray-300">
|
||||
{isExpanded ? <ChevronDown size={18} /> : <ChevronRight size={18} />}
|
||||
</Typography.Text>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
import { Typography } from "#/ui/typography";
|
||||
import { ToolParameters } from "./tool-parameters";
|
||||
import { ToggleButton } from "./toggle-button";
|
||||
|
||||
interface FunctionData {
|
||||
name?: string;
|
||||
description?: string;
|
||||
parameters?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface ToolData {
|
||||
type?: string;
|
||||
function?: FunctionData;
|
||||
name?: string;
|
||||
description?: string;
|
||||
parameters?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface ToolItemProps {
|
||||
tool: Record<string, unknown>;
|
||||
index: number;
|
||||
isExpanded: boolean;
|
||||
onToggle: (index: number) => void;
|
||||
}
|
||||
|
||||
export function ToolItem({ tool, index, isExpanded, onToggle }: ToolItemProps) {
|
||||
// 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;
|
||||
|
||||
return (
|
||||
<div className="rounded-md overflow-hidden">
|
||||
<ToggleButton
|
||||
title={String(name)}
|
||||
isExpanded={isExpanded}
|
||||
onClick={() => onToggle(index)}
|
||||
/>
|
||||
|
||||
{isExpanded && (
|
||||
<div className="px-2 pb-3 pt-1">
|
||||
<div className="mt-2 mb-3">
|
||||
<Typography.Text className="text-sm whitespace-pre-wrap text-gray-300 leading-relaxed">
|
||||
{String(description)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
{/* Parameters section */}
|
||||
{parameters && <ToolParameters parameters={parameters} />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ReactJsonView from "@microlink/react-json-view";
|
||||
import { JSON_VIEW_THEME } from "#/utils/constants";
|
||||
import { Typography } from "#/ui/typography";
|
||||
|
||||
interface ToolParametersProps {
|
||||
parameters: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export function ToolParameters({ parameters }: ToolParametersProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="mt-2">
|
||||
<Typography.Text className="text-sm font-semibold text-gray-300">
|
||||
{t("SYSTEM_MESSAGE_MODAL$PARAMETERS")}
|
||||
</Typography.Text>
|
||||
<div className="text-sm mt-2 p-3 bg-gray-900 rounded-md overflow-auto text-gray-300 max-h-[400px] shadow-inner">
|
||||
<ReactJsonView name={false} src={parameters} theme={JSON_VIEW_THEME} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
import { ToolItem } from "./tool-item";
|
||||
|
||||
interface ToolsListProps {
|
||||
tools: Array<Record<string, unknown>>;
|
||||
expandedTools: Record<number, boolean>;
|
||||
onToggleTool: (index: number) => void;
|
||||
}
|
||||
|
||||
export function ToolsList({
|
||||
tools,
|
||||
expandedTools,
|
||||
onToggleTool,
|
||||
}: ToolsListProps) {
|
||||
return (
|
||||
<div className="p-2 space-y-3">
|
||||
{tools.map((tool, index) => (
|
||||
<ToolItem
|
||||
key={index}
|
||||
tool={tool}
|
||||
index={index}
|
||||
isExpanded={expandedTools[index] || false}
|
||||
onToggle={onToggleTool}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import { SystemMessageModal } from "../conversation-panel/system-message-modal";
|
||||
import { MicroagentsModal } from "../conversation-panel/microagents-modal";
|
||||
import { ConfirmDeleteModal } from "../conversation-panel/confirm-delete-modal";
|
||||
import { ConfirmStopModal } from "../conversation-panel/confirm-stop-modal";
|
||||
import { MetricsModal } from "./metrics-modal/metrics-modal";
|
||||
import { MetricsModal } from "./metrics-modal";
|
||||
|
||||
export function ConversationName() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { BaseModal } from "../../shared/modals/base-modal/base-modal";
|
||||
import { BudgetDisplay } from "../conversation-panel/budget-display";
|
||||
import { RootState } from "#/store";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
interface MetricsModalProps {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export function MetricsModal({ isOpen, onOpenChange }: MetricsModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const metrics = useSelector((state: RootState) => state.metrics);
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
isOpen={isOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
title={t(I18nKey.CONVERSATION$METRICS_INFO)}
|
||||
testID="metrics-modal"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{(metrics?.cost !== null || metrics?.usage !== null) && (
|
||||
<div className="rounded-md p-3">
|
||||
<div className="grid gap-3">
|
||||
{metrics?.cost !== null && (
|
||||
<div className="flex justify-between items-center pb-2">
|
||||
<span className="text-lg font-semibold">
|
||||
{t(I18nKey.CONVERSATION$TOTAL_COST)}
|
||||
</span>
|
||||
<span className="font-semibold">
|
||||
${metrics.cost.toFixed(4)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<BudgetDisplay
|
||||
cost={metrics?.cost ?? null}
|
||||
maxBudgetPerTask={metrics?.max_budget_per_task ?? null}
|
||||
/>
|
||||
|
||||
{metrics?.usage !== null && (
|
||||
<>
|
||||
<div className="flex justify-between items-center pb-2">
|
||||
<span>{t(I18nKey.CONVERSATION$INPUT)}</span>
|
||||
<span className="font-semibold">
|
||||
{metrics.usage.prompt_tokens.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2 pl-4 text-sm">
|
||||
<span className="text-neutral-400">
|
||||
{t(I18nKey.CONVERSATION$CACHE_HIT)}
|
||||
</span>
|
||||
<span className="text-right">
|
||||
{metrics.usage.cache_read_tokens.toLocaleString()}
|
||||
</span>
|
||||
<span className="text-neutral-400">
|
||||
{t(I18nKey.CONVERSATION$CACHE_WRITE)}
|
||||
</span>
|
||||
<span className="text-right">
|
||||
{metrics.usage.cache_write_tokens.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center border-b border-neutral-700 pb-2">
|
||||
<span>{t(I18nKey.CONVERSATION$OUTPUT)}</span>
|
||||
<span className="font-semibold">
|
||||
{metrics.usage.completion_tokens.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center border-b border-neutral-700 pb-2">
|
||||
<span className="font-semibold">
|
||||
{t(I18nKey.CONVERSATION$TOTAL)}
|
||||
</span>
|
||||
<span className="font-bold">
|
||||
{(
|
||||
metrics.usage.prompt_tokens +
|
||||
metrics.usage.completion_tokens
|
||||
).toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-semibold">
|
||||
{t(I18nKey.CONVERSATION$CONTEXT_WINDOW)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full h-1.5 bg-neutral-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-blue-500 transition-all duration-300"
|
||||
style={{
|
||||
width: `${Math.min(100, (metrics.usage.per_turn_token / metrics.usage.context_window) * 100)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<span className="text-xs text-neutral-400">
|
||||
{metrics.usage.per_turn_token.toLocaleString()} /{" "}
|
||||
{metrics.usage.context_window.toLocaleString()} (
|
||||
{(
|
||||
(metrics.usage.per_turn_token /
|
||||
metrics.usage.context_window) *
|
||||
100
|
||||
).toFixed(2)}
|
||||
% {t(I18nKey.CONVERSATION$USED)})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!metrics?.cost && !metrics?.usage && (
|
||||
<div className="rounded-md p-4 text-center">
|
||||
<p className="text-neutral-400">
|
||||
{t(I18nKey.CONVERSATION$NO_METRICS)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
interface ContextWindowSectionProps {
|
||||
perTurnToken: number;
|
||||
contextWindow: number;
|
||||
}
|
||||
|
||||
export function ContextWindowSection({
|
||||
perTurnToken,
|
||||
contextWindow,
|
||||
}: ContextWindowSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const usagePercentage = (perTurnToken / contextWindow) * 100;
|
||||
const progressWidth = Math.min(100, usagePercentage);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-semibold">
|
||||
{t(I18nKey.CONVERSATION$CONTEXT_WINDOW)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full h-1.5 bg-neutral-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-blue-500 transition-all duration-300"
|
||||
style={{ width: `${progressWidth}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<span className="text-xs text-neutral-400">
|
||||
{perTurnToken.toLocaleString()} / {contextWindow.toLocaleString()} (
|
||||
{usagePercentage.toFixed(2)}% {t(I18nKey.CONVERSATION$USED)})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BudgetDisplay } from "../../conversation-panel/budget-display";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
interface CostSectionProps {
|
||||
cost: number | null;
|
||||
maxBudgetPerTask: number | null;
|
||||
}
|
||||
|
||||
export function CostSection({ cost, maxBudgetPerTask }: CostSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (cost === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex justify-between items-center pb-2">
|
||||
<span className="text-lg font-semibold">
|
||||
{t(I18nKey.CONVERSATION$TOTAL_COST)}
|
||||
</span>
|
||||
<span className="font-semibold">${cost.toFixed(4)}</span>
|
||||
</div>
|
||||
<BudgetDisplay cost={cost} maxBudgetPerTask={maxBudgetPerTask} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
export function EmptyState() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="rounded-md p-4 text-center">
|
||||
<p className="text-neutral-400">{t(I18nKey.CONVERSATION$NO_METRICS)}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
interface MetricRowProps {
|
||||
label: ReactNode;
|
||||
value: ReactNode;
|
||||
labelClassName?: string;
|
||||
valueClassName?: string;
|
||||
}
|
||||
|
||||
export function MetricRow({
|
||||
label,
|
||||
value,
|
||||
labelClassName = "",
|
||||
valueClassName = "font-semibold",
|
||||
}: MetricRowProps) {
|
||||
return (
|
||||
<div className="flex justify-between items-center border-b border-neutral-700 pb-2">
|
||||
<span className={labelClassName}>{label}</span>
|
||||
<span className={valueClassName}>{value}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { BaseModal } from "../../../shared/modals/base-modal/base-modal";
|
||||
import { RootState } from "#/store";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { CostSection } from "./cost-section";
|
||||
import { UsageSection } from "./usage-section";
|
||||
import { ContextWindowSection } from "./context-window-section";
|
||||
import { EmptyState } from "./empty-state";
|
||||
|
||||
interface MetricsModalProps {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export function MetricsModal({ isOpen, onOpenChange }: MetricsModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const metrics = useSelector((state: RootState) => state.metrics);
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
isOpen={isOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
title={t(I18nKey.CONVERSATION$METRICS_INFO)}
|
||||
testID="metrics-modal"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{(metrics?.cost !== null || metrics?.usage !== null) && (
|
||||
<div className="rounded-md p-3">
|
||||
<div className="grid gap-3">
|
||||
<CostSection
|
||||
cost={metrics?.cost ?? null}
|
||||
maxBudgetPerTask={metrics?.max_budget_per_task ?? null}
|
||||
/>
|
||||
|
||||
{metrics?.usage !== null && (
|
||||
<>
|
||||
<UsageSection usage={metrics.usage} />
|
||||
<ContextWindowSection
|
||||
perTurnToken={metrics.usage.per_turn_token}
|
||||
contextWindow={metrics.usage.context_window}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!metrics?.cost && !metrics?.usage && <EmptyState />}
|
||||
</div>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { MetricRow } from "./metric-row";
|
||||
|
||||
interface UsageSectionProps {
|
||||
usage: {
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
cache_read_tokens: number;
|
||||
cache_write_tokens: number;
|
||||
};
|
||||
}
|
||||
|
||||
export function UsageSection({ usage }: UsageSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<MetricRow
|
||||
label={t(I18nKey.CONVERSATION$INPUT)}
|
||||
value={usage.prompt_tokens.toLocaleString()}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2 pl-4 text-sm">
|
||||
<span className="text-neutral-400">
|
||||
{t(I18nKey.CONVERSATION$CACHE_HIT)}
|
||||
</span>
|
||||
<span className="text-right">
|
||||
{usage.cache_read_tokens.toLocaleString()}
|
||||
</span>
|
||||
<span className="text-neutral-400">
|
||||
{t(I18nKey.CONVERSATION$CACHE_WRITE)}
|
||||
</span>
|
||||
<span className="text-right">
|
||||
{usage.cache_write_tokens.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<MetricRow
|
||||
label={t(I18nKey.CONVERSATION$OUTPUT)}
|
||||
value={usage.completion_tokens.toLocaleString()}
|
||||
/>
|
||||
|
||||
<MetricRow
|
||||
label={t(I18nKey.CONVERSATION$TOTAL)}
|
||||
value={(usage.prompt_tokens + usage.completion_tokens).toLocaleString()}
|
||||
labelClassName="font-semibold"
|
||||
valueClassName="font-bold"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -526,12 +526,14 @@ function LlmSettingsScreen() {
|
||||
/>
|
||||
)}
|
||||
|
||||
<HelpLink
|
||||
testId="search-api-key-help-anchor"
|
||||
text={t(I18nKey.SETTINGS$SEARCH_API_KEY_OPTIONAL)}
|
||||
linkText={t(I18nKey.SETTINGS$SEARCH_API_KEY_INSTRUCTIONS)}
|
||||
href="https://tavily.com/"
|
||||
/>
|
||||
{config?.APP_MODE !== "saas" && (
|
||||
<HelpLink
|
||||
testId="search-api-key-help-anchor"
|
||||
text={t(I18nKey.SETTINGS$SEARCH_API_KEY_OPTIONAL)}
|
||||
linkText={t(I18nKey.SETTINGS$SEARCH_API_KEY_INSTRUCTIONS)}
|
||||
href="https://tavily.com/"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "#/utils/utils";
|
||||
|
||||
const preVariants = cva("whitespace-pre-wrap", {
|
||||
variants: {
|
||||
size: {
|
||||
default: "text-sm",
|
||||
small: "text-xs",
|
||||
},
|
||||
font: {
|
||||
default: "",
|
||||
mono: "font-mono",
|
||||
},
|
||||
lineHeight: {
|
||||
default: "",
|
||||
relaxed: "leading-relaxed",
|
||||
},
|
||||
background: {
|
||||
default: "",
|
||||
dark: "bg-gray-900",
|
||||
},
|
||||
textColor: {
|
||||
default: "",
|
||||
light: "text-gray-300",
|
||||
},
|
||||
padding: {
|
||||
default: "",
|
||||
medium: "p-3",
|
||||
large: "px-5",
|
||||
},
|
||||
borderRadius: {
|
||||
default: "",
|
||||
medium: "rounded-md",
|
||||
},
|
||||
shadow: {
|
||||
default: "",
|
||||
inner: "shadow-inner",
|
||||
},
|
||||
maxHeight: {
|
||||
default: "",
|
||||
small: "max-h-[400px]",
|
||||
large: "max-h-[60vh]",
|
||||
},
|
||||
overflow: {
|
||||
default: "",
|
||||
auto: "overflow-auto",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: "default",
|
||||
font: "default",
|
||||
lineHeight: "default",
|
||||
background: "default",
|
||||
textColor: "default",
|
||||
padding: "default",
|
||||
borderRadius: "default",
|
||||
shadow: "default",
|
||||
maxHeight: "default",
|
||||
overflow: "default",
|
||||
},
|
||||
});
|
||||
|
||||
interface PreProps extends VariantProps<typeof preVariants> {
|
||||
className?: string;
|
||||
testId?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Pre({
|
||||
size,
|
||||
font,
|
||||
lineHeight,
|
||||
background,
|
||||
textColor,
|
||||
padding,
|
||||
borderRadius,
|
||||
shadow,
|
||||
maxHeight,
|
||||
overflow,
|
||||
className,
|
||||
testId,
|
||||
children,
|
||||
}: PreProps) {
|
||||
return (
|
||||
<pre
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
preVariants({
|
||||
size,
|
||||
font,
|
||||
lineHeight,
|
||||
background,
|
||||
textColor,
|
||||
padding,
|
||||
borderRadius,
|
||||
shadow,
|
||||
maxHeight,
|
||||
overflow,
|
||||
}),
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
@@ -6,8 +6,6 @@ const typographyVariants = cva("", {
|
||||
variant: {
|
||||
h1: "text-[32px] text-white font-bold leading-5",
|
||||
span: "text-sm font-normal text-white leading-5.5",
|
||||
codeBlock:
|
||||
"font-mono text-sm leading-relaxed text-gray-300 whitespace-pre-wrap",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
@@ -64,19 +62,6 @@ export function Text({
|
||||
);
|
||||
}
|
||||
|
||||
export function CodeBlock({
|
||||
className,
|
||||
testId,
|
||||
children,
|
||||
}: Omit<TypographyProps, "variant">) {
|
||||
return (
|
||||
<Typography variant="codeBlock" className={className} testId={testId}>
|
||||
{children}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
// Attach components to Typography for the expected API
|
||||
Typography.H1 = H1;
|
||||
Typography.Text = Text;
|
||||
Typography.CodeBlock = CodeBlock;
|
||||
|
||||
@@ -502,11 +502,13 @@ class LLM(RetryMixin, DebugMixin):
|
||||
|
||||
# Set max_output_tokens from model info if not explicitly set
|
||||
if self.config.max_output_tokens is None:
|
||||
# Special case for Claude 3.7 Sonnet models
|
||||
if any(
|
||||
model in self.config.model
|
||||
for model in ['claude-3-7-sonnet', 'claude-3.7-sonnet']
|
||||
):
|
||||
# Special case for Claude Sonnet models
|
||||
sonnet_models = [
|
||||
'claude-3-7-sonnet',
|
||||
'claude-3.7-sonnet',
|
||||
'claude-sonnet-4',
|
||||
]
|
||||
if any(model in self.config.model for model in sonnet_models):
|
||||
self.config.max_output_tokens = 64000 # litellm set max to 128k, but that requires a header to be set
|
||||
# Try to get from model info
|
||||
elif self.model_info is not None:
|
||||
|
||||
@@ -129,15 +129,11 @@ class ActionExecutionClient(Runtime):
|
||||
return send_request(self.session, method, url, **kwargs)
|
||||
|
||||
def check_if_alive(self) -> None:
|
||||
request_url = f'{self.action_execution_server_url}/alive'
|
||||
self.log('debug', f'Sending request to: {request_url}')
|
||||
response = self._send_action_server_request(
|
||||
'GET',
|
||||
request_url,
|
||||
f'{self.action_execution_server_url}/alive',
|
||||
timeout=5,
|
||||
)
|
||||
self.log('debug', f'Response status code: {response.status_code}')
|
||||
self.log('debug', f'Response text: {response.text}')
|
||||
assert response.is_closed
|
||||
|
||||
def list_files(self, path: str | None = None) -> list[str]:
|
||||
|
||||
@@ -40,7 +40,7 @@ Two configuration options are required to use the Kubernetes runtime:
|
||||
2. **Runtime Container Image**: Specify the container image to use for the runtime environment
|
||||
```toml
|
||||
[sandbox]
|
||||
runtime_container_image = "docker.all-hands.dev/all-hands-ai/runtime:0.56-nikolaik"
|
||||
runtime_container_image = "docker.all-hands.dev/all-hands-ai/runtime:0.57-nikolaik"
|
||||
```
|
||||
|
||||
#### Additional Kubernetes Options
|
||||
|
||||
@@ -415,19 +415,11 @@ class RemoteRuntime(ActionExecutionClient):
|
||||
|
||||
def _wait_until_alive_impl(self) -> None:
|
||||
self.log('debug', f'Waiting for runtime to be alive at url: {self.runtime_url}')
|
||||
self.log(
|
||||
'debug',
|
||||
f'Sending request to: {self.config.sandbox.remote_runtime_api_url}/runtime/{self.runtime_id}',
|
||||
)
|
||||
runtime_info_response = self._send_runtime_api_request(
|
||||
'GET',
|
||||
f'{self.config.sandbox.remote_runtime_api_url}/runtime/{self.runtime_id}',
|
||||
)
|
||||
runtime_data = runtime_info_response.json()
|
||||
self.log(
|
||||
'debug',
|
||||
f'received response: {runtime_data}',
|
||||
)
|
||||
assert 'runtime_id' in runtime_data
|
||||
assert runtime_data['runtime_id'] == self.runtime_id
|
||||
assert 'pod_status' in runtime_data
|
||||
|
||||
@@ -15,8 +15,6 @@ from openhands.runtime.plugins.requirement import Plugin, PluginRequirement
|
||||
from openhands.runtime.utils.system import check_port_available
|
||||
from openhands.utils.shutdown_listener import should_continue
|
||||
|
||||
RUNTIME_USERNAME = os.getenv('RUNTIME_USERNAME')
|
||||
|
||||
|
||||
@dataclass
|
||||
class VSCodeRequirement(PluginRequirement):
|
||||
@@ -39,7 +37,7 @@ class VSCodePlugin(Plugin):
|
||||
)
|
||||
return
|
||||
|
||||
if username not in filter(None, [RUNTIME_USERNAME, 'root', 'openhands']):
|
||||
if username not in ['root', 'openhands']:
|
||||
self.vscode_port = None
|
||||
self.vscode_connection_token = None
|
||||
logger.warning(
|
||||
|
||||
@@ -20,8 +20,6 @@ from openhands.events.observation.commands import (
|
||||
from openhands.runtime.utils.bash_constants import TIMEOUT_MESSAGE_TEMPLATE
|
||||
from openhands.utils.shutdown_listener import should_continue
|
||||
|
||||
RUNTIME_USERNAME = os.getenv('RUNTIME_USERNAME')
|
||||
|
||||
|
||||
def split_bash_commands(commands: str) -> list[str]:
|
||||
if not commands.strip():
|
||||
@@ -195,7 +193,7 @@ class BashSession:
|
||||
def initialize(self) -> None:
|
||||
self.server = libtmux.Server()
|
||||
_shell_command = '/bin/bash'
|
||||
if self.username in list(filter(None, [RUNTIME_USERNAME, 'root', 'openhands'])):
|
||||
if self.username in ['root', 'openhands']:
|
||||
# This starts a non-login (new) shell for the given user
|
||||
_shell_command = f'su {self.username} -'
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import os
|
||||
|
||||
from openhands.core.config import OpenHandsConfig
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.runtime.plugins import PluginRequirement
|
||||
@@ -14,9 +12,6 @@ DEFAULT_PYTHON_PREFIX = [
|
||||
]
|
||||
DEFAULT_MAIN_MODULE = 'openhands.runtime.action_execution_server'
|
||||
|
||||
RUNTIME_USERNAME = os.getenv('RUNTIME_USERNAME')
|
||||
RUNTIME_UID = os.getenv('RUNTIME_UID')
|
||||
|
||||
|
||||
def get_action_execution_server_startup_command(
|
||||
server_port: int,
|
||||
@@ -31,10 +26,7 @@ def get_action_execution_server_startup_command(
|
||||
sandbox_config = app_config.sandbox
|
||||
logger.debug(f'app_config {vars(app_config)}')
|
||||
logger.debug(f'sandbox_config {vars(sandbox_config)}')
|
||||
logger.debug(f'RUNTIME_USERNAME {RUNTIME_USERNAME}, RUNTIME_UID {RUNTIME_UID}')
|
||||
logger.debug(
|
||||
f'override_username {override_username}, override_user_id {override_user_id}'
|
||||
)
|
||||
logger.debug(f'override_user_id {override_user_id}')
|
||||
|
||||
# Plugin args
|
||||
plugin_args = []
|
||||
@@ -48,15 +40,10 @@ def get_action_execution_server_startup_command(
|
||||
'--browsergym-eval-env'
|
||||
] + sandbox_config.browsergym_eval_env.split(' ')
|
||||
|
||||
username = (
|
||||
override_username
|
||||
or RUNTIME_USERNAME
|
||||
or ('openhands' if app_config.run_as_openhands else 'root')
|
||||
username = override_username or (
|
||||
'openhands' if app_config.run_as_openhands else 'root'
|
||||
)
|
||||
user_id = (
|
||||
override_user_id or RUNTIME_UID or (1000 if app_config.run_as_openhands else 0)
|
||||
)
|
||||
logger.debug(f'username {username}, user_id {user_id}')
|
||||
user_id = override_user_id or (1000 if app_config.run_as_openhands else 0)
|
||||
|
||||
base_cmd = [
|
||||
*python_prefix,
|
||||
|
||||
@@ -27,7 +27,6 @@ class HttpSession:
|
||||
headers = kwargs.get('headers') or {}
|
||||
headers = {**self.headers, **headers}
|
||||
kwargs['headers'] = headers
|
||||
logger.debug(f'HttpSession:request called with args {args} and kwargs {kwargs}')
|
||||
return CLIENT.request(*args, **kwargs)
|
||||
|
||||
def stream(self, *args, **kwargs):
|
||||
|
||||
Generated
+2
-2
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiofiles"
|
||||
@@ -11823,4 +11823,4 @@ third-party-runtimes = ["daytona", "e2b-code-interpreter", "modal", "runloop-api
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = "^3.12,<3.14"
|
||||
content-hash = "6c7bc9a39d6875e09966872a5d579e73b5cb739d1bad89d3a7dde829541cec16"
|
||||
content-hash = "3b77845e0da841c359d325b3a8ee210528b0ed60a2b6e57cd931a7506692c41e"
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ requires = [
|
||||
|
||||
[tool.poetry]
|
||||
name = "openhands-ai"
|
||||
version = "0.56.0"
|
||||
version = "0.57.2"
|
||||
description = "OpenHands: Code Less, Make More"
|
||||
authors = [ "OpenHands" ]
|
||||
license = "MIT"
|
||||
@@ -26,7 +26,7 @@ build = "build_vscode.py" # Build VSCode extension during Poetry build
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.12,<3.14"
|
||||
litellm = "^1.74.3, !=1.64.4, !=1.67.*" # avoid 1.64.4 (known bug) & 1.67.* (known bug #10272)
|
||||
litellm = ">=1.74.3, <1.77.2, !=1.64.4, !=1.67.*" # avoid 1.64.4 (known bug) & 1.67.* (known bug #10272)
|
||||
openai = "1.99.9" # Pin due to litellm incompatibility with >=1.100.0 (BerriAI/litellm#13711)
|
||||
aiohttp = ">=3.9.0,!=3.11.13" # Pin to avoid yanked version 3.11.13
|
||||
google-genai = "*" # To use litellm with Gemini Pro API
|
||||
|
||||
@@ -1053,17 +1053,22 @@ def test_claude_3_7_sonnet_max_output_tokens():
|
||||
assert llm.config.max_input_tokens is None
|
||||
|
||||
|
||||
def test_claude_sonnet_4_max_output_tokens():
|
||||
@patch('openhands.llm.llm.litellm.get_model_info')
|
||||
def test_claude_sonnet_4_max_output_tokens(mock_get_model_info):
|
||||
"""Test that Claude Sonnet 4 models get the correct max_output_tokens and max_input_tokens values."""
|
||||
mock_get_model_info.return_value = {
|
||||
'max_input_tokens': 100000,
|
||||
'max_output_tokens': 100000,
|
||||
}
|
||||
# Create LLM instance with a Claude Sonnet 4 model
|
||||
config = LLMConfig(model='claude-sonnet-4-20250514', api_key='test_key')
|
||||
llm = LLM(config, service_id='test-service')
|
||||
llm.init_model_info()
|
||||
|
||||
# Verify max_output_tokens is set to the expected value
|
||||
assert llm.config.max_output_tokens == 64000
|
||||
# Verify max_input_tokens is set to the expected value
|
||||
# For Claude models, we expect a specific value from litellm
|
||||
assert llm.config.max_input_tokens == 200000
|
||||
assert llm.config.max_output_tokens == 64000, 'output max should be decreased'
|
||||
assert llm.config.max_input_tokens == 100000, (
|
||||
'input max should be the litellm value'
|
||||
)
|
||||
|
||||
|
||||
def test_sambanova_deepseek_model_max_output_tokens():
|
||||
|
||||
Reference in New Issue
Block a user