feat(backend): Support CreateMicroagent in the “Create New Conversation” API (#9765)

This commit is contained in:
Hiep Le 2025-07-18 12:31:09 +07:00 committed by GitHub
parent cb910e6863
commit 67edc66da7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 190 additions and 0 deletions

View File

@ -22,6 +22,7 @@ class TaskType(str, Enum):
UNRESOLVED_COMMENTS = 'UNRESOLVED_COMMENTS'
OPEN_ISSUE = 'OPEN_ISSUE'
OPEN_PR = 'OPEN_PR'
CREATE_MICROAGENT = 'CREATE_MICROAGENT'
class OwnerType(str, Enum):
@ -98,6 +99,12 @@ class SuggestedTask(BaseModel):
return template.render(issue_number=issue_number, repo=repo, **terms)
class CreateMicroagent(BaseModel):
repo: str
git_provider: ProviderType | None = None
title: str | None = None
class User(BaseModel):
id: str
login: str

View File

@ -27,6 +27,7 @@ from openhands.integrations.provider import (
)
from openhands.integrations.service_types import (
AuthenticationError,
CreateMicroagent,
ProviderType,
SuggestedTask,
)
@ -84,6 +85,7 @@ class InitSessionRequest(BaseModel):
image_urls: list[str] | None = None
replay_json: str | None = None
suggested_task: SuggestedTask | None = None
create_microagent: CreateMicroagent | None = None
conversation_instructions: str | None = None
# Only nested runtimes require the ability to specify a conversation id, and it could be a security risk
if os.getenv('ALLOW_SET_CONVERSATION_ID', '0') == '1':
@ -123,6 +125,7 @@ async def new_conversation(
image_urls = data.image_urls or []
replay_json = data.replay_json
suggested_task = data.suggested_task
create_microagent = data.create_microagent
git_provider = data.git_provider
conversation_instructions = data.conversation_instructions
@ -131,6 +134,13 @@ async def new_conversation(
if suggested_task:
initial_user_msg = suggested_task.get_prompt_for_task()
conversation_trigger = ConversationTrigger.SUGGESTED_TASK
elif create_microagent:
conversation_trigger = ConversationTrigger.MICROAGENT_MANAGEMENT
# Set repository and git_provider from create_microagent if not already set
if not repository and create_microagent.repo:
repository = create_microagent.repo
if not git_provider and create_microagent.git_provider:
git_provider = create_microagent.git_provider
if auth_type == AuthType.BEARER:
conversation_trigger = ConversationTrigger.REMOTE_API_KEY

View File

@ -11,6 +11,7 @@ class ConversationTrigger(Enum):
SUGGESTED_TASK = 'suggested_task'
REMOTE_API_KEY = 'openhands_api'
SLACK = 'slack'
MICROAGENT_MANAGEMENT = 'microagent_management'
@dataclass

View File

@ -11,6 +11,7 @@ from fastapi.testclient import TestClient
from openhands.integrations.service_types import (
AuthenticationError,
CreateMicroagent,
ProviderType,
SuggestedTask,
TaskType,
@ -608,3 +609,174 @@ async def test_new_conversation_with_unsupported_params():
# Verify that the error message mentions the unsupported parameter
assert 'Extra inputs are not permitted' in str(excinfo.value)
assert 'unsupported_param' in str(excinfo.value)
@pytest.mark.asyncio
async def test_new_conversation_with_create_microagent(provider_handler_mock):
"""Test creating a new conversation with a CreateMicroagent object."""
with _patch_store():
# Mock the create_new_conversation function directly
with patch(
'openhands.server.routes.manage_conversations.create_new_conversation'
) as mock_create_conversation:
# Set up the mock to return a conversation ID
mock_create_conversation.return_value = MagicMock(
conversation_id='test_conversation_id',
url='https://my-conversation.com',
session_api_key=None,
status=ConversationStatus.RUNNING,
)
# Create the CreateMicroagent object
create_microagent = CreateMicroagent(
repo='test/repo',
git_provider=ProviderType.GITHUB,
title='Create a new microagent',
)
test_request = InitSessionRequest(
repository=None, # Not set in request, should be set from create_microagent
selected_branch='main',
initial_user_msg='Hello, agent!',
create_microagent=create_microagent,
)
# Call new_conversation
response = await create_new_test_conversation(test_request)
# Verify the response
assert isinstance(response, ConversationResponse)
assert response.status == 'ok'
assert response.conversation_id is not None
assert isinstance(response.conversation_id, str)
# Verify that create_new_conversation was called with the correct arguments
mock_create_conversation.assert_called_once()
call_args = mock_create_conversation.call_args[1]
assert call_args['user_id'] == 'test_user'
assert (
call_args['selected_repository'] == 'test/repo'
) # Should be set from create_microagent
assert call_args['selected_branch'] == 'main'
assert call_args['initial_user_msg'] == 'Hello, agent!'
assert (
call_args['conversation_trigger']
== ConversationTrigger.MICROAGENT_MANAGEMENT
)
assert (
call_args['git_provider'] == ProviderType.GITHUB
) # Should be set from create_microagent
@pytest.mark.asyncio
async def test_new_conversation_with_create_microagent_repository_override(
provider_handler_mock,
):
"""Test creating a new conversation with CreateMicroagent when repository is already set."""
with _patch_store():
# Mock the create_new_conversation function directly
with patch(
'openhands.server.routes.manage_conversations.create_new_conversation'
) as mock_create_conversation:
# Set up the mock to return a conversation ID
mock_create_conversation.return_value = MagicMock(
conversation_id='test_conversation_id',
url='https://my-conversation.com',
session_api_key=None,
status=ConversationStatus.RUNNING,
)
# Create the CreateMicroagent object
create_microagent = CreateMicroagent(
repo='microagent/repo',
git_provider=ProviderType.GITLAB,
title='Create a new microagent',
)
test_request = InitSessionRequest(
repository='existing/repo', # Already set in request
selected_branch='main',
initial_user_msg='Hello, agent!',
create_microagent=create_microagent,
)
# Call new_conversation
response = await create_new_test_conversation(test_request)
# Verify the response
assert isinstance(response, ConversationResponse)
assert response.status == 'ok'
assert response.conversation_id is not None
assert isinstance(response.conversation_id, str)
# Verify that create_new_conversation was called with the correct arguments
mock_create_conversation.assert_called_once()
call_args = mock_create_conversation.call_args[1]
assert call_args['user_id'] == 'test_user'
assert (
call_args['selected_repository'] == 'existing/repo'
) # Should keep existing value
assert call_args['selected_branch'] == 'main'
assert call_args['initial_user_msg'] == 'Hello, agent!'
assert (
call_args['conversation_trigger']
== ConversationTrigger.MICROAGENT_MANAGEMENT
)
assert (
call_args['git_provider'] == ProviderType.GITLAB
) # Should be set from create_microagent
@pytest.mark.asyncio
async def test_new_conversation_with_create_microagent_minimal(provider_handler_mock):
"""Test creating a new conversation with minimal CreateMicroagent object (only repo field)."""
with _patch_store():
# Mock the create_new_conversation function directly
with patch(
'openhands.server.routes.manage_conversations.create_new_conversation'
) as mock_create_conversation:
# Set up the mock to return a conversation ID
mock_create_conversation.return_value = MagicMock(
conversation_id='test_conversation_id',
url='https://my-conversation.com',
session_api_key=None,
status=ConversationStatus.RUNNING,
)
# Create the CreateMicroagent object with only required field
create_microagent = CreateMicroagent(
repo='minimal/repo',
)
test_request = InitSessionRequest(
repository=None,
selected_branch='main',
initial_user_msg='Hello, agent!',
create_microagent=create_microagent,
)
# Call new_conversation
response = await create_new_test_conversation(test_request)
# Verify the response
assert isinstance(response, ConversationResponse)
assert response.status == 'ok'
assert response.conversation_id is not None
assert isinstance(response.conversation_id, str)
# Verify that create_new_conversation was called with the correct arguments
mock_create_conversation.assert_called_once()
call_args = mock_create_conversation.call_args[1]
assert call_args['user_id'] == 'test_user'
assert (
call_args['selected_repository'] == 'minimal/repo'
) # Should be set from create_microagent
assert call_args['selected_branch'] == 'main'
assert call_args['initial_user_msg'] == 'Hello, agent!'
assert (
call_args['conversation_trigger']
== ConversationTrigger.MICROAGENT_MANAGEMENT
)
assert (
call_args['git_provider'] is None
) # Should remain None since not set in create_microagent