mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
(Hotfix): tokens go stale for restarted convos in cloud openhands (#9111)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
parent
075ef4db9f
commit
ee64a6662a
@ -293,9 +293,11 @@ class OpenHands {
|
||||
|
||||
static async startConversation(
|
||||
conversationId: string,
|
||||
providers?: Provider[],
|
||||
): Promise<Conversation | null> {
|
||||
const { data } = await openHands.post<Conversation | null>(
|
||||
`/api/conversations/${conversationId}/start`,
|
||||
providers ? { providers_set: providers } : {},
|
||||
);
|
||||
|
||||
return data;
|
||||
|
||||
@ -348,6 +348,7 @@ export function WsClientProvider({
|
||||
conversation?.url,
|
||||
conversation?.status,
|
||||
conversation?.runtime_status,
|
||||
providers,
|
||||
]);
|
||||
|
||||
React.useEffect(
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
import React from "react";
|
||||
import { convertRawProvidersToList } from "#/utils/convert-raw-providers-to-list";
|
||||
import { useSettings } from "./query/use-settings";
|
||||
|
||||
export const useUserProviders = () => {
|
||||
const { data: settings } = useSettings();
|
||||
|
||||
const providers = React.useMemo(
|
||||
() => convertRawProvidersToList(settings?.PROVIDER_TOKENS_SET),
|
||||
[settings?.PROVIDER_TOKENS_SET],
|
||||
);
|
||||
|
||||
return {
|
||||
providers: convertRawProvidersToList(settings?.PROVIDER_TOKENS_SET),
|
||||
providers,
|
||||
};
|
||||
};
|
||||
|
||||
@ -37,6 +37,7 @@ import { transformVSCodeUrl } from "#/utils/vscode-url-helper";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { TabContent } from "#/components/layout/tab-content";
|
||||
import { useIsAuthed } from "#/hooks/query/use-is-authed";
|
||||
import { useUserProviders } from "#/hooks/use-user-providers";
|
||||
|
||||
function AppContent() {
|
||||
useConversationConfig();
|
||||
@ -45,6 +46,7 @@ function AppContent() {
|
||||
const { conversationId } = useConversationId();
|
||||
const { data: conversation, isFetched, refetch } = useActiveConversation();
|
||||
const { data: isAuthed } = useIsAuthed();
|
||||
const { providers } = useUserProviders();
|
||||
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const dispatch = useDispatch();
|
||||
@ -63,11 +65,11 @@ function AppContent() {
|
||||
navigate("/");
|
||||
} else if (conversation?.status === "STOPPED") {
|
||||
// start the conversation if the state is stopped on initial load
|
||||
OpenHands.startConversation(conversation.conversation_id).then(() =>
|
||||
refetch(),
|
||||
OpenHands.startConversation(conversation.conversation_id, providers).then(
|
||||
() => refetch(),
|
||||
);
|
||||
}
|
||||
}, [conversation?.conversation_id, isFetched, isAuthed]);
|
||||
}, [conversation?.conversation_id, isFetched, isAuthed, providers]);
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch(clearTerminal());
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import asyncio
|
||||
import os
|
||||
from types import MappingProxyType
|
||||
from typing import Any
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
@ -20,66 +19,17 @@ from openhands.events.observation.agent import (
|
||||
AgentStateChangedObservation,
|
||||
)
|
||||
from openhands.events.serialization import event_to_dict
|
||||
from openhands.experiments.experiment_manager import ExperimentManagerImpl
|
||||
from openhands.integrations.provider import PROVIDER_TOKEN_TYPE, ProviderToken
|
||||
from openhands.integrations.service_types import ProviderType
|
||||
from openhands.server.session.conversation_init_data import ConversationInitData
|
||||
from openhands.server.services.conversation_service import (
|
||||
setup_init_convo_settings,
|
||||
)
|
||||
from openhands.server.shared import (
|
||||
SecretsStoreImpl,
|
||||
SettingsStoreImpl,
|
||||
config,
|
||||
conversation_manager,
|
||||
server_config,
|
||||
sio,
|
||||
)
|
||||
from openhands.server.types import AppMode
|
||||
from openhands.storage.conversation.conversation_validator import (
|
||||
create_conversation_validator,
|
||||
)
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
|
||||
|
||||
def create_provider_tokens_object(
|
||||
providers_set: list[ProviderType],
|
||||
) -> PROVIDER_TOKEN_TYPE:
|
||||
provider_information: dict[ProviderType, ProviderToken] = {}
|
||||
|
||||
for provider in providers_set:
|
||||
provider_information[provider] = ProviderToken(token=None, user_id=None)
|
||||
|
||||
return MappingProxyType(provider_information)
|
||||
|
||||
|
||||
async def setup_init_convo_settings(
|
||||
user_id: str | None, conversation_id: str, providers_set: list[ProviderType]
|
||||
) -> ConversationInitData:
|
||||
settings_store = await SettingsStoreImpl.get_instance(config, user_id)
|
||||
settings = await settings_store.load()
|
||||
|
||||
secrets_store = await SecretsStoreImpl.get_instance(config, user_id)
|
||||
user_secrets: UserSecrets | None = await secrets_store.load()
|
||||
|
||||
if not settings:
|
||||
raise ConnectionRefusedError(
|
||||
'Settings not found', {'msg_id': 'CONFIGURATION$SETTINGS_NOT_FOUND'}
|
||||
)
|
||||
|
||||
session_init_args: dict = {}
|
||||
session_init_args = {**settings.__dict__, **session_init_args}
|
||||
|
||||
git_provider_tokens = create_provider_tokens_object(providers_set)
|
||||
if server_config.app_mode != AppMode.SAAS and user_secrets:
|
||||
git_provider_tokens = user_secrets.provider_tokens
|
||||
|
||||
session_init_args['git_provider_tokens'] = git_provider_tokens
|
||||
if user_secrets:
|
||||
session_init_args['custom_secrets'] = user_secrets.custom_secrets
|
||||
|
||||
convo_init_data = ConversationInitData(**session_init_args)
|
||||
# We should recreate the same experiment conditions when restarting a conversation
|
||||
return ExperimentManagerImpl.run_conversation_variant_test(
|
||||
user_id, conversation_id, convo_init_data
|
||||
)
|
||||
|
||||
|
||||
@sio.event
|
||||
@ -170,6 +120,7 @@ async def connect(connection_id: str, environ: dict) -> None:
|
||||
conversation_init_data = await setup_init_convo_settings(
|
||||
user_id, conversation_id, providers_set
|
||||
)
|
||||
|
||||
agent_loop_info = await conversation_manager.join_conversation(
|
||||
conversation_id,
|
||||
connection_id,
|
||||
|
||||
@ -38,7 +38,10 @@ from openhands.server.data_models.conversation_info_result_set import (
|
||||
ConversationInfoResultSet,
|
||||
)
|
||||
from openhands.server.dependencies import get_dependencies
|
||||
from openhands.server.services.conversation_service import create_new_conversation
|
||||
from openhands.server.services.conversation_service import (
|
||||
create_new_conversation,
|
||||
setup_init_convo_settings,
|
||||
)
|
||||
from openhands.server.session.conversation import ServerConversation
|
||||
from openhands.server.shared import (
|
||||
ConversationStoreImpl,
|
||||
@ -95,6 +98,10 @@ class ConversationResponse(BaseModel):
|
||||
conversation_status: ConversationStatus | None = None
|
||||
|
||||
|
||||
class ProvidersSetModel(BaseModel):
|
||||
providers_set: list[ProviderType] | None = None
|
||||
|
||||
|
||||
@app.post('/conversations')
|
||||
async def new_conversation(
|
||||
data: InitSessionRequest,
|
||||
@ -395,6 +402,7 @@ async def _get_conversation_info(
|
||||
@app.post('/conversations/{conversation_id}/start')
|
||||
async def start_conversation(
|
||||
conversation_id: str,
|
||||
providers_set: ProvidersSetModel,
|
||||
user_id: str = Depends(get_user_id),
|
||||
settings: Settings = Depends(get_user_settings),
|
||||
conversation_store: ConversationStore = Depends(get_conversation_store),
|
||||
@ -420,10 +428,15 @@ async def start_conversation(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
# Set up conversation init data with provider information
|
||||
conversation_init_data = await setup_init_convo_settings(
|
||||
user_id, conversation_id, providers_set.providers_set or []
|
||||
)
|
||||
|
||||
# Start the agent loop
|
||||
agent_loop_info = await conversation_manager.maybe_start_agent_loop(
|
||||
sid=conversation_id,
|
||||
settings=settings,
|
||||
settings=conversation_init_data,
|
||||
user_id=user_id,
|
||||
)
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import uuid
|
||||
from types import MappingProxyType
|
||||
from typing import Any
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
@ -7,21 +8,25 @@ from openhands.experiments.experiment_manager import ExperimentManagerImpl
|
||||
from openhands.integrations.provider import (
|
||||
CUSTOM_SECRETS_TYPE_WITH_JSON_SCHEMA,
|
||||
PROVIDER_TOKEN_TYPE,
|
||||
ProviderToken,
|
||||
)
|
||||
from openhands.integrations.service_types import ProviderType
|
||||
from openhands.server.data_models.agent_loop_info import AgentLoopInfo
|
||||
from openhands.server.session.conversation_init_data import ConversationInitData
|
||||
from openhands.server.shared import (
|
||||
ConversationStoreImpl,
|
||||
SecretsStoreImpl,
|
||||
SettingsStoreImpl,
|
||||
config,
|
||||
conversation_manager,
|
||||
server_config,
|
||||
)
|
||||
from openhands.server.types import LLMAuthenticationError, MissingSettingsError
|
||||
from openhands.server.types import AppMode, LLMAuthenticationError, MissingSettingsError
|
||||
from openhands.storage.data_models.conversation_metadata import (
|
||||
ConversationMetadata,
|
||||
ConversationTrigger,
|
||||
)
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
from openhands.utils.conversation_summary import get_default_conversation_title
|
||||
|
||||
|
||||
@ -135,3 +140,62 @@ async def create_new_conversation(
|
||||
)
|
||||
logger.info(f'Finished initializing conversation {agent_loop_info.conversation_id}')
|
||||
return agent_loop_info
|
||||
|
||||
|
||||
def create_provider_tokens_object(
|
||||
providers_set: list[ProviderType],
|
||||
) -> PROVIDER_TOKEN_TYPE:
|
||||
"""Create provider tokens object for the given providers."""
|
||||
provider_information: dict[ProviderType, ProviderToken] = {}
|
||||
|
||||
for provider in providers_set:
|
||||
provider_information[provider] = ProviderToken(token=None, user_id=None)
|
||||
|
||||
return MappingProxyType(provider_information)
|
||||
|
||||
|
||||
async def setup_init_convo_settings(
|
||||
user_id: str | None, conversation_id: str, providers_set: list[ProviderType]
|
||||
) -> ConversationInitData:
|
||||
"""Set up conversation initialization data with provider tokens.
|
||||
|
||||
|
||||
Args:
|
||||
user_id: The user ID
|
||||
conversation_id: The conversation ID
|
||||
providers_set: List of provider types to set up tokens for
|
||||
|
||||
Returns:
|
||||
ConversationInitData with provider tokens configured
|
||||
"""
|
||||
settings_store = await SettingsStoreImpl.get_instance(config, user_id)
|
||||
settings = await settings_store.load()
|
||||
|
||||
secrets_store = await SecretsStoreImpl.get_instance(config, user_id)
|
||||
user_secrets: UserSecrets | None = await secrets_store.load()
|
||||
|
||||
if not settings:
|
||||
from socketio.exceptions import ConnectionRefusedError
|
||||
|
||||
raise ConnectionRefusedError(
|
||||
'Settings not found', {'msg_id': 'CONFIGURATION$SETTINGS_NOT_FOUND'}
|
||||
)
|
||||
|
||||
session_init_args: dict = {}
|
||||
session_init_args = {**settings.__dict__, **session_init_args}
|
||||
|
||||
git_provider_tokens = create_provider_tokens_object(providers_set)
|
||||
logger.info(f'Git provider scaffold: {git_provider_tokens}')
|
||||
|
||||
if server_config.app_mode != AppMode.SAAS and user_secrets:
|
||||
git_provider_tokens = user_secrets.provider_tokens
|
||||
|
||||
session_init_args['git_provider_tokens'] = git_provider_tokens
|
||||
if user_secrets:
|
||||
session_init_args['custom_secrets'] = user_secrets.custom_secrets
|
||||
|
||||
convo_init_data = ConversationInitData(**session_init_args)
|
||||
# We should recreate the same experiment conditions when restarting a conversation
|
||||
return ExperimentManagerImpl.run_conversation_variant_test(
|
||||
user_id, conversation_id, convo_init_data
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user