mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
4 Commits
fix/events
...
update-con
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a47ca4693 | ||
|
|
19ed3fe95c | ||
|
|
6a29f0513d | ||
|
|
bd79a90ade |
@@ -72,9 +72,9 @@ describe("ActionSuggestions", () => {
|
||||
|
||||
it("should render both GitHub buttons when GitHub token is set and repository is selected", async () => {
|
||||
const getConversationSpy = vi.spyOn(OpenHands, "getConversation");
|
||||
// @ts-expect-error - only required for testing
|
||||
getConversationSpy.mockResolvedValue({
|
||||
selected_repository: "test-repo",
|
||||
// @ts-expect-error - only required for testing
|
||||
repository: { full_name: "test-repo" },
|
||||
});
|
||||
renderActionSuggestions();
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ describe("ConversationPanel", () => {
|
||||
{
|
||||
conversation_id: "1",
|
||||
title: "Conversation 1",
|
||||
selected_repository: null,
|
||||
repository: null,
|
||||
last_updated_at: "2021-10-01T12:00:00Z",
|
||||
created_at: "2021-10-01T12:00:00Z",
|
||||
status: "STOPPED" as const,
|
||||
@@ -51,7 +51,7 @@ describe("ConversationPanel", () => {
|
||||
{
|
||||
conversation_id: "2",
|
||||
title: "Conversation 2",
|
||||
selected_repository: null,
|
||||
repository: null,
|
||||
last_updated_at: "2021-10-02T12:00:00Z",
|
||||
created_at: "2021-10-02T12:00:00Z",
|
||||
status: "STOPPED" as const,
|
||||
@@ -61,7 +61,7 @@ describe("ConversationPanel", () => {
|
||||
{
|
||||
conversation_id: "3",
|
||||
title: "Conversation 3",
|
||||
selected_repository: null,
|
||||
repository: null,
|
||||
last_updated_at: "2021-10-03T12:00:00Z",
|
||||
created_at: "2021-10-03T12:00:00Z",
|
||||
status: "STOPPED" as const,
|
||||
@@ -145,7 +145,7 @@ describe("ConversationPanel", () => {
|
||||
{
|
||||
conversation_id: "1",
|
||||
title: "Conversation 1",
|
||||
selected_repository: null,
|
||||
repository: null,
|
||||
last_updated_at: "2021-10-01T12:00:00Z",
|
||||
created_at: "2021-10-01T12:00:00Z",
|
||||
status: "STOPPED" as const,
|
||||
@@ -155,7 +155,7 @@ describe("ConversationPanel", () => {
|
||||
{
|
||||
conversation_id: "2",
|
||||
title: "Conversation 2",
|
||||
selected_repository: null,
|
||||
repository: null,
|
||||
last_updated_at: "2021-10-02T12:00:00Z",
|
||||
created_at: "2021-10-02T12:00:00Z",
|
||||
status: "STOPPED" as const,
|
||||
@@ -165,7 +165,7 @@ describe("ConversationPanel", () => {
|
||||
{
|
||||
conversation_id: "3",
|
||||
title: "Conversation 3",
|
||||
selected_repository: null,
|
||||
repository: null,
|
||||
last_updated_at: "2021-10-03T12:00:00Z",
|
||||
created_at: "2021-10-03T12:00:00Z",
|
||||
status: "STOPPED" as const,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ProjectStatus } from "#/components/features/conversation-panel/conversation-state-indicator";
|
||||
import { Provider } from "#/types/settings";
|
||||
|
||||
export interface ErrorResponse {
|
||||
error: string;
|
||||
@@ -72,10 +73,17 @@ export interface AuthenticateResponse {
|
||||
|
||||
export type ConversationTrigger = "resolver" | "gui" | "suggested_task";
|
||||
|
||||
export interface RepositoryInfo {
|
||||
full_name: string;
|
||||
id: number | null;
|
||||
git_provider: Provider | null;
|
||||
is_public: boolean | null;
|
||||
}
|
||||
|
||||
export interface Conversation {
|
||||
conversation_id: string;
|
||||
title: string;
|
||||
selected_repository: string | null;
|
||||
repository: RepositoryInfo | null;
|
||||
last_updated_at: string;
|
||||
created_at: string;
|
||||
status: ProjectStatus;
|
||||
|
||||
@@ -38,7 +38,7 @@ export function ActionSuggestions({
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 mb-2">
|
||||
{providersAreSet && conversation?.selected_repository && (
|
||||
{providersAreSet && conversation?.repository && (
|
||||
<div className="flex flex-row gap-2 justify-center w-full">
|
||||
{!hasPullRequest ? (
|
||||
<>
|
||||
|
||||
@@ -29,7 +29,7 @@ export function Controls({ setSecurityOpen, showSecurityLock }: ControlsProps) {
|
||||
showOptions
|
||||
title={conversation?.title ?? ""}
|
||||
lastUpdatedAt={conversation?.created_at ?? ""}
|
||||
selectedRepository={conversation?.selected_repository ?? null}
|
||||
selectedRepository={conversation?.repository?.full_name ?? null}
|
||||
status={conversation?.status}
|
||||
conversationId={conversation?.conversation_id}
|
||||
/>
|
||||
|
||||
@@ -88,7 +88,7 @@ export function ConversationPanel({ onClose }: ConversationPanelProps) {
|
||||
isActive={isActive}
|
||||
onDelete={() => handleDeleteProject(project.conversation_id)}
|
||||
title={project.title}
|
||||
selectedRepository={project.selected_repository}
|
||||
selectedRepository={project.repository?.full_name ?? null}
|
||||
lastUpdatedAt={project.last_updated_at}
|
||||
createdAt={project.created_at}
|
||||
status={project.status}
|
||||
|
||||
@@ -236,7 +236,7 @@ export function WsClientProvider({
|
||||
conversationId,
|
||||
]);
|
||||
const clonedRepositoryDirectory =
|
||||
cachedConversaton?.selected_repository?.split("/").pop();
|
||||
cachedConversaton?.repository?.full_name?.split("/").pop();
|
||||
|
||||
let fileToInvalidate = event.args.path.replace("/workspace/", "");
|
||||
if (clonedRepositoryDirectory) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// this file generate by script, don't modify it manually!!!
|
||||
export enum I18nKey {
|
||||
HOME$LAUNCH_FROM_SCRATCH = "HOME$LAUNCH_FROM_SCRATCH",
|
||||
HOME$READ_THIS = "HOME$READ_THIS",
|
||||
AUTH$LOGGING_BACK_IN = "AUTH$LOGGING_BACK_IN",
|
||||
SECURITY$LOW_RISK = "SECURITY$LOW_RISK",
|
||||
SECURITY$MEDIUM_RISK = "SECURITY$MEDIUM_RISK",
|
||||
|
||||
@@ -51,7 +51,7 @@ const conversations: Conversation[] = [
|
||||
{
|
||||
conversation_id: "1",
|
||||
title: "My New Project",
|
||||
selected_repository: null,
|
||||
repository: null,
|
||||
last_updated_at: new Date().toISOString(),
|
||||
created_at: new Date().toISOString(),
|
||||
status: "RUNNING",
|
||||
@@ -61,7 +61,12 @@ const conversations: Conversation[] = [
|
||||
{
|
||||
conversation_id: "2",
|
||||
title: "Repo Testing",
|
||||
selected_repository: "octocat/hello-world",
|
||||
repository: {
|
||||
id: 2,
|
||||
full_name: "octocat/hello-world",
|
||||
git_provider: "github",
|
||||
is_public: true,
|
||||
},
|
||||
// 2 days ago
|
||||
last_updated_at: new Date(
|
||||
Date.now() - 2 * 24 * 60 * 60 * 1000,
|
||||
@@ -74,7 +79,12 @@ const conversations: Conversation[] = [
|
||||
{
|
||||
conversation_id: "3",
|
||||
title: "Another Project",
|
||||
selected_repository: "octocat/earth",
|
||||
repository: {
|
||||
id: 3,
|
||||
full_name: "octocat/earth",
|
||||
git_provider: "github",
|
||||
is_public: true,
|
||||
},
|
||||
// 5 days ago
|
||||
last_updated_at: new Date(
|
||||
Date.now() - 5 * 24 * 60 * 60 * 1000,
|
||||
@@ -270,7 +280,7 @@ export const handlers = [
|
||||
const conversation: Conversation = {
|
||||
conversation_id: (Math.random() * 100).toString(),
|
||||
title: "New Conversation",
|
||||
selected_repository: null,
|
||||
repository: null,
|
||||
last_updated_at: new Date().toISOString(),
|
||||
created_at: new Date().toISOString(),
|
||||
status: "RUNNING",
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from openhands.integrations.service_types import ProviderType
|
||||
from openhands.storage.data_models.conversation_metadata import ConversationTrigger
|
||||
from openhands.storage.data_models.conversation_status import ConversationStatus
|
||||
|
||||
|
||||
@dataclass
|
||||
class RepositoryInfo:
|
||||
"""
|
||||
Information about a repository associated with a conversation
|
||||
"""
|
||||
|
||||
full_name: str
|
||||
id: int | None = None
|
||||
git_provider: ProviderType | None = None
|
||||
is_public: bool | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConversationInfo:
|
||||
"""
|
||||
@@ -16,9 +29,9 @@ class ConversationInfo:
|
||||
title: str
|
||||
last_updated_at: datetime | None = None
|
||||
status: ConversationStatus = ConversationStatus.STOPPED
|
||||
selected_repository: str | None = None
|
||||
trigger: ConversationTrigger | None = None
|
||||
num_connections: int = 0
|
||||
url: str | None = None
|
||||
session_api_key: str | None = None
|
||||
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
repository: RepositoryInfo | None = None
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
|
||||
@@ -18,7 +17,10 @@ from openhands.integrations.service_types import (
|
||||
)
|
||||
from openhands.runtime import get_runtime_cls
|
||||
from openhands.server.data_models.agent_loop_info import AgentLoopInfo
|
||||
from openhands.server.data_models.conversation_info import ConversationInfo
|
||||
from openhands.server.data_models.conversation_info import (
|
||||
ConversationInfo,
|
||||
RepositoryInfo,
|
||||
)
|
||||
from openhands.server.data_models.conversation_info_result_set import (
|
||||
ConversationInfoResultSet,
|
||||
)
|
||||
@@ -106,8 +108,10 @@ async def new_conversation(
|
||||
if auth_type == AuthType.BEARER:
|
||||
conversation_trigger = ConversationTrigger.REMOTE_API_KEY
|
||||
|
||||
|
||||
if conversation_trigger == ConversationTrigger.REMOTE_API_KEY and not initial_user_msg:
|
||||
if (
|
||||
conversation_trigger == ConversationTrigger.REMOTE_API_KEY
|
||||
and not initial_user_msg
|
||||
):
|
||||
return JSONResponse(
|
||||
content={
|
||||
'status': 'error',
|
||||
@@ -118,12 +122,25 @@ async def new_conversation(
|
||||
)
|
||||
|
||||
try:
|
||||
repo_details = None
|
||||
if repository:
|
||||
provider_handler = ProviderHandler(provider_tokens)
|
||||
# Check against git_provider, otherwise check all provider apis
|
||||
await provider_handler.verify_repo_provider(repository, git_provider)
|
||||
repo_details = await provider_handler.verify_repo_provider(
|
||||
repository, git_provider
|
||||
)
|
||||
|
||||
conversation_id = data.conversation_id
|
||||
# Extract repository details if available
|
||||
repository_id = None
|
||||
is_public = None
|
||||
actual_git_provider = git_provider
|
||||
|
||||
if repo_details:
|
||||
repository_id = repo_details.id
|
||||
is_public = repo_details.is_public
|
||||
actual_git_provider = repo_details.git_provider
|
||||
|
||||
await create_new_conversation(
|
||||
user_id=user_id,
|
||||
git_provider_tokens=provider_tokens,
|
||||
@@ -135,8 +152,10 @@ async def new_conversation(
|
||||
replay_json=replay_json,
|
||||
conversation_trigger=conversation_trigger,
|
||||
conversation_instructions=conversation_instructions,
|
||||
git_provider=git_provider,
|
||||
git_provider=actual_git_provider,
|
||||
conversation_id=conversation_id,
|
||||
repository_id=repository_id,
|
||||
is_public=is_public,
|
||||
)
|
||||
|
||||
return InitSessionResponse(
|
||||
@@ -196,19 +215,27 @@ async def search_conversations(
|
||||
conversation_ids = set(
|
||||
conversation.conversation_id for conversation in filtered_results
|
||||
)
|
||||
connection_ids_to_conversation_ids = await conversation_manager.get_connections(filter_to_sids=conversation_ids)
|
||||
agent_loop_info = await conversation_manager.get_agent_loop_info(filter_to_sids=conversation_ids)
|
||||
agent_loop_info_by_conversation_id = {info.conversation_id: info for info in agent_loop_info}
|
||||
connection_ids_to_conversation_ids = await conversation_manager.get_connections(
|
||||
filter_to_sids=conversation_ids
|
||||
)
|
||||
agent_loop_info = await conversation_manager.get_agent_loop_info(
|
||||
filter_to_sids=conversation_ids
|
||||
)
|
||||
agent_loop_info_by_conversation_id = {
|
||||
info.conversation_id: info for info in agent_loop_info
|
||||
}
|
||||
result = ConversationInfoResultSet(
|
||||
results=await wait_all(
|
||||
_get_conversation_info(
|
||||
conversation=conversation,
|
||||
num_connections=sum(
|
||||
1 for conversation_id in connection_ids_to_conversation_ids.values()
|
||||
1
|
||||
for conversation_id in connection_ids_to_conversation_ids.values()
|
||||
if conversation_id == conversation.conversation_id
|
||||
),
|
||||
agent_loop_info=agent_loop_info_by_conversation_id.get(conversation.conversation_id),
|
||||
|
||||
agent_loop_info=agent_loop_info_by_conversation_id.get(
|
||||
conversation.conversation_id
|
||||
),
|
||||
)
|
||||
for conversation in filtered_results
|
||||
),
|
||||
@@ -224,10 +251,16 @@ async def get_conversation(
|
||||
) -> ConversationInfo | None:
|
||||
try:
|
||||
metadata = await conversation_store.get_metadata(conversation_id)
|
||||
num_connections = len(await conversation_manager.get_connections(filter_to_sids={conversation_id}))
|
||||
agent_loop_infos = await conversation_manager.get_agent_loop_info(filter_to_sids={conversation_id})
|
||||
num_connections = len(
|
||||
await conversation_manager.get_connections(filter_to_sids={conversation_id})
|
||||
)
|
||||
agent_loop_infos = await conversation_manager.get_agent_loop_info(
|
||||
filter_to_sids={conversation_id}
|
||||
)
|
||||
agent_loop_info = agent_loop_infos[0] if agent_loop_infos else None
|
||||
conversation_info = await _get_conversation_info(metadata, num_connections, agent_loop_info)
|
||||
conversation_info = await _get_conversation_info(
|
||||
metadata, num_connections, agent_loop_info
|
||||
)
|
||||
return conversation_info
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
@@ -261,19 +294,34 @@ async def _get_conversation_info(
|
||||
title = conversation.title
|
||||
if not title:
|
||||
title = get_default_conversation_title(conversation.conversation_id)
|
||||
|
||||
# Create repository info if a repository is selected
|
||||
repository = None
|
||||
if conversation.selected_repository:
|
||||
repository = RepositoryInfo(
|
||||
full_name=conversation.selected_repository,
|
||||
id=conversation.repository_id,
|
||||
git_provider=conversation.git_provider,
|
||||
is_public=conversation.is_public,
|
||||
)
|
||||
|
||||
return ConversationInfo(
|
||||
trigger=conversation.trigger,
|
||||
conversation_id=conversation.conversation_id,
|
||||
title=title,
|
||||
last_updated_at=conversation.last_updated_at,
|
||||
created_at=conversation.created_at,
|
||||
selected_repository=conversation.selected_repository,
|
||||
status=(
|
||||
agent_loop_info.status if agent_loop_info else ConversationStatus.STOPPED
|
||||
agent_loop_info.status
|
||||
if agent_loop_info
|
||||
else ConversationStatus.STOPPED
|
||||
),
|
||||
num_connections=num_connections,
|
||||
url=agent_loop_info.url if agent_loop_info else None,
|
||||
session_api_key=agent_loop_info.session_api_key if agent_loop_info else None,
|
||||
session_api_key=agent_loop_info.session_api_key
|
||||
if agent_loop_info
|
||||
else None,
|
||||
repository=repository,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
|
||||
@@ -38,6 +38,8 @@ async def create_new_conversation(
|
||||
attach_convo_id: bool = False,
|
||||
git_provider: ProviderType | None = None,
|
||||
conversation_id: str | None = None,
|
||||
repository_id: int | None = None,
|
||||
is_public: bool | None = None,
|
||||
) -> AgentLoopInfo:
|
||||
logger.info(
|
||||
'Creating conversation',
|
||||
@@ -103,6 +105,9 @@ async def create_new_conversation(
|
||||
user_id=user_id,
|
||||
selected_repository=selected_repository,
|
||||
selected_branch=selected_branch,
|
||||
git_provider=git_provider,
|
||||
repository_id=repository_id,
|
||||
is_public=is_public,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
|
||||
from openhands.integrations.service_types import ProviderType
|
||||
|
||||
|
||||
class ConversationTrigger(Enum):
|
||||
RESOLVER = 'resolver'
|
||||
@@ -20,10 +22,14 @@ class ConversationMetadata:
|
||||
title: str | None = None
|
||||
last_updated_at: datetime | None = None
|
||||
trigger: ConversationTrigger | None = None
|
||||
pr_number: list[int] = field(default_factory=list)
|
||||
pr_number: list[int] = field(default_factory=list)
|
||||
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
# Cost and token metrics
|
||||
accumulated_cost: float = 0.0
|
||||
prompt_tokens: int = 0
|
||||
completion_tokens: int = 0
|
||||
total_tokens: int = 0
|
||||
# Additional repository fields
|
||||
repository_id: int | None = None
|
||||
git_provider: ProviderType | None = None
|
||||
is_public: bool | None = None
|
||||
|
||||
Reference in New Issue
Block a user