diff --git a/frontend/src/api/conversation-service/v1-conversation-service.api.ts b/frontend/src/api/conversation-service/v1-conversation-service.api.ts index 5343ded874..717228c79f 100644 --- a/frontend/src/api/conversation-service/v1-conversation-service.api.ts +++ b/frontend/src/api/conversation-service/v1-conversation-service.api.ts @@ -11,7 +11,6 @@ import type { V1AppConversationStartTask, V1AppConversationStartTaskPage, V1AppConversation, - V1SandboxInfo, } from "./v1-conversation-service.types"; class V1ConversationService { @@ -213,36 +212,6 @@ class V1ConversationService { return data; } - /** - * Pause a V1 sandbox - * Calls the /api/v1/sandboxes/{id}/pause endpoint - * - * @param sandboxId The sandbox ID to pause - * @returns Success response - */ - static async pauseSandbox(sandboxId: string): Promise<{ success: boolean }> { - const { data } = await openHands.post<{ success: boolean }>( - `/api/v1/sandboxes/${sandboxId}/pause`, - {}, - ); - return data; - } - - /** - * Resume a V1 sandbox - * Calls the /api/v1/sandboxes/{id}/resume endpoint - * - * @param sandboxId The sandbox ID to resume - * @returns Success response - */ - static async resumeSandbox(sandboxId: string): Promise<{ success: boolean }> { - const { data } = await openHands.post<{ success: boolean }>( - `/api/v1/sandboxes/${sandboxId}/resume`, - {}, - ); - return data; - } - /** * Batch get V1 app conversations by their IDs * Returns null for any missing conversations @@ -269,32 +238,6 @@ class V1ConversationService { return data; } - /** - * Batch get V1 sandboxes by their IDs - * Returns null for any missing sandboxes - * - * @param ids Array of sandbox IDs (max 100) - * @returns Array of sandboxes or null for missing ones - */ - static async batchGetSandboxes( - ids: string[], - ): Promise<(V1SandboxInfo | null)[]> { - if (ids.length === 0) { - return []; - } - if (ids.length > 100) { - throw new Error("Cannot request more than 100 sandboxes at once"); - } - - const params = new URLSearchParams(); - ids.forEach((id) => params.append("id", id)); - - const { data } = await openHands.get<(V1SandboxInfo | null)[]>( - `/api/v1/sandboxes?${params.toString()}`, - ); - return data; - } - /** * Upload a single file to the V1 conversation workspace * V1 API endpoint: POST /api/file/upload/{path} @@ -345,24 +288,6 @@ class V1ConversationService { const { data } = await openHands.get<{ runtime_id: string }>(url); return data; } - - /** - * Get the count of events for a conversation - * Uses the V1 API endpoint: GET /api/v1/events/count - * - * @param conversationId The conversation ID to get event count for - * @returns The number of events in the conversation - */ - static async getEventCount(conversationId: string): Promise { - const params = new URLSearchParams(); - params.append("conversation_id__eq", conversationId); - - const { data } = await openHands.get( - `/api/v1/events/count?${params.toString()}`, - ); - - return data; - } } export default V1ConversationService; diff --git a/frontend/src/api/conversation-service/v1-conversation-service.types.ts b/frontend/src/api/conversation-service/v1-conversation-service.types.ts index f1206fc382..4ab05fbc8e 100644 --- a/frontend/src/api/conversation-service/v1-conversation-service.types.ts +++ b/frontend/src/api/conversation-service/v1-conversation-service.types.ts @@ -1,5 +1,6 @@ import { ConversationTrigger } from "../open-hands.types"; import { Provider } from "#/types/settings"; +import { V1SandboxStatus } from "../sandbox-service/sandbox-service.types"; // V1 API Types for requests // Note: This represents the serialized API format, not the internal TextContent/ImageContent types @@ -64,13 +65,6 @@ export interface V1AppConversationStartTaskPage { next_page_id: string | null; } -export type V1SandboxStatus = - | "MISSING" - | "STARTING" - | "RUNNING" - | "STOPPED" - | "PAUSED"; - export type V1AgentExecutionStatus = | "RUNNING" | "AWAITING_USER_INPUT" @@ -98,18 +92,3 @@ export interface V1AppConversation { conversation_url: string | null; session_api_key: string | null; } - -export interface V1ExposedUrl { - name: string; - url: string; -} - -export interface V1SandboxInfo { - id: string; - created_by_user_id: string | null; - sandbox_spec_id: string; - status: V1SandboxStatus; - session_api_key: string | null; - exposed_urls: V1ExposedUrl[] | null; - created_at: string; -} diff --git a/frontend/src/api/event-service/event-service.api.ts b/frontend/src/api/event-service/event-service.api.ts index 90a1d4e64e..3e7a42666b 100644 --- a/frontend/src/api/event-service/event-service.api.ts +++ b/frontend/src/api/event-service/event-service.api.ts @@ -5,6 +5,7 @@ import type { ConfirmationResponseRequest, ConfirmationResponseResponse, } from "./event-service.types"; +import { openHands } from "../open-hands-axios"; class EventService { /** @@ -36,6 +37,14 @@ class EventService { return data; } -} + static async getEventCount(conversationId: string): Promise { + const params = new URLSearchParams(); + params.append("conversation_id__eq", conversationId); + const { data } = await openHands.get( + `/api/v1/events/count?${params.toString()}`, + ); + return data; + } +} export default EventService; diff --git a/frontend/src/api/sandbox-service/sandbox-service.api.ts b/frontend/src/api/sandbox-service/sandbox-service.api.ts new file mode 100644 index 0000000000..6855b5e61d --- /dev/null +++ b/frontend/src/api/sandbox-service/sandbox-service.api.ts @@ -0,0 +1,52 @@ +// sandbox-service.api.ts +// This file contains API methods for /api/v1/sandboxes endpoints. + +import { openHands } from "../open-hands-axios"; +import type { V1SandboxInfo } from "./sandbox-service.types"; + +export class SandboxService { + /** + * Pause a V1 sandbox + * Calls the /api/v1/sandboxes/{id}/pause endpoint + */ + static async pauseSandbox(sandboxId: string): Promise<{ success: boolean }> { + const { data } = await openHands.post<{ success: boolean }>( + `/api/v1/sandboxes/${sandboxId}/pause`, + {}, + ); + return data; + } + + /** + * Resume a V1 sandbox + * Calls the /api/v1/sandboxes/{id}/resume endpoint + */ + static async resumeSandbox(sandboxId: string): Promise<{ success: boolean }> { + const { data } = await openHands.post<{ success: boolean }>( + `/api/v1/sandboxes/${sandboxId}/resume`, + {}, + ); + return data; + } + + /** + * Batch get V1 sandboxes by their IDs + * Returns null for any missing sandboxes + */ + static async batchGetSandboxes( + ids: string[], + ): Promise<(V1SandboxInfo | null)[]> { + if (ids.length === 0) { + return []; + } + if (ids.length > 100) { + throw new Error("Cannot request more than 100 sandboxes at once"); + } + const params = new URLSearchParams(); + ids.forEach((id) => params.append("id", id)); + const { data } = await openHands.get<(V1SandboxInfo | null)[]>( + `/api/v1/sandboxes?${params.toString()}`, + ); + return data; + } +} diff --git a/frontend/src/api/sandbox-service/sandbox-service.types.ts b/frontend/src/api/sandbox-service/sandbox-service.types.ts new file mode 100644 index 0000000000..6e9d30b581 --- /dev/null +++ b/frontend/src/api/sandbox-service/sandbox-service.types.ts @@ -0,0 +1,24 @@ +// sandbox-service.types.ts +// This file contains types for Sandbox API. + +export type V1SandboxStatus = + | "MISSING" + | "STARTING" + | "RUNNING" + | "STOPPED" + | "PAUSED"; + +export interface V1ExposedUrl { + name: string; + url: string; +} + +export interface V1SandboxInfo { + id: string; + created_by_user_id: string | null; + sandbox_spec_id: string; + status: V1SandboxStatus; + session_api_key: string | null; + exposed_urls: V1ExposedUrl[] | null; + created_at: string; +} diff --git a/frontend/src/contexts/conversation-websocket-context.tsx b/frontend/src/contexts/conversation-websocket-context.tsx index 0be6e75393..c8b7a644e6 100644 --- a/frontend/src/contexts/conversation-websocket-context.tsx +++ b/frontend/src/contexts/conversation-websocket-context.tsx @@ -28,7 +28,7 @@ import { import { handleActionEventCacheInvalidation } from "#/utils/cache-utils"; import { buildWebSocketUrl } from "#/utils/websocket-url"; import type { V1SendMessageRequest } from "#/api/conversation-service/v1-conversation-service.types"; -import V1ConversationService from "#/api/conversation-service/v1-conversation-service.api"; +import EventService from "#/api/event-service/event-service.api"; // eslint-disable-next-line @typescript-eslint/naming-convention export type V1_WebSocketConnectionState = @@ -211,8 +211,7 @@ export function ConversationWebSocketProvider({ // Fetch expected event count for history loading detection if (conversationId) { try { - const count = - await V1ConversationService.getEventCount(conversationId); + const count = await EventService.getEventCount(conversationId); setExpectedEventCount(count); // If no events expected, mark as loaded immediately diff --git a/frontend/src/hooks/mutation/conversation-mutation-utils.ts b/frontend/src/hooks/mutation/conversation-mutation-utils.ts index 4c14d18337..70b570e32d 100644 --- a/frontend/src/hooks/mutation/conversation-mutation-utils.ts +++ b/frontend/src/hooks/mutation/conversation-mutation-utils.ts @@ -2,6 +2,7 @@ import { QueryClient } from "@tanstack/react-query"; import { Provider } from "#/types/settings"; import ConversationService from "#/api/conversation-service/conversation-service.api"; import V1ConversationService from "#/api/conversation-service/v1-conversation-service.api"; +import { SandboxService } from "#/api/sandbox-service/sandbox-service.api"; /** * Gets the conversation version from the cache @@ -48,7 +49,7 @@ const fetchV1ConversationData = async ( */ export const pauseV1ConversationSandbox = async (conversationId: string) => { const { sandboxId } = await fetchV1ConversationData(conversationId); - return V1ConversationService.pauseSandbox(sandboxId); + return SandboxService.pauseSandbox(sandboxId); }; /** @@ -75,7 +76,7 @@ export const stopV0Conversation = async (conversationId: string) => */ export const resumeV1ConversationSandbox = async (conversationId: string) => { const { sandboxId } = await fetchV1ConversationData(conversationId); - return V1ConversationService.resumeSandbox(sandboxId); + return SandboxService.resumeSandbox(sandboxId); }; /** diff --git a/frontend/src/hooks/query/use-batch-sandboxes.ts b/frontend/src/hooks/query/use-batch-sandboxes.ts index bf4f456114..8310ee3aad 100644 --- a/frontend/src/hooks/query/use-batch-sandboxes.ts +++ b/frontend/src/hooks/query/use-batch-sandboxes.ts @@ -1,10 +1,10 @@ import { useQuery } from "@tanstack/react-query"; -import V1ConversationService from "#/api/conversation-service/v1-conversation-service.api"; +import { SandboxService } from "#/api/sandbox-service/sandbox-service.api"; export const useBatchSandboxes = (ids: string[]) => useQuery({ queryKey: ["sandboxes", "batch", ids], - queryFn: () => V1ConversationService.batchGetSandboxes(ids), + queryFn: () => SandboxService.batchGetSandboxes(ids), enabled: ids.length > 0, staleTime: 1000 * 60 * 5, // 5 minutes gcTime: 1000 * 60 * 15, // 15 minutes