mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Engel Nyst <engel.nyst@gmail.com> Co-authored-by: tofarr <tofarr@gmail.com> Co-authored-by: Engel Nyst <enyst@users.noreply.github.com> Co-authored-by: Robert Brennan <accounts@rbren.io> Co-authored-by: Rohit Malhotra <rohitvinodmalhotra@gmail.com>
483 lines
13 KiB
TypeScript
483 lines
13 KiB
TypeScript
import { AxiosHeaders } from "axios";
|
|
import {
|
|
Feedback,
|
|
FeedbackResponse,
|
|
GitHubAccessTokenResponse,
|
|
GetConfigResponse,
|
|
GetVSCodeUrlResponse,
|
|
AuthenticateResponse,
|
|
Conversation,
|
|
ResultSet,
|
|
GetTrajectoryResponse,
|
|
GitChangeDiff,
|
|
GitChange,
|
|
GetMicroagentsResponse,
|
|
GetMicroagentPromptResponse,
|
|
} from "./open-hands.types";
|
|
import { openHands } from "./open-hands-axios";
|
|
import { ApiSettings, PostApiSettings, Provider } from "#/types/settings";
|
|
import { GitUser, GitRepository, Branch } from "#/types/git";
|
|
import { SuggestedTask } from "#/components/features/home/tasks/task.types";
|
|
|
|
class OpenHands {
|
|
private static currentConversation: Conversation | null = null;
|
|
|
|
/**
|
|
* Get a current conversation
|
|
* @return the current conversation
|
|
*/
|
|
static getCurrentConversation(): Conversation | null {
|
|
return this.currentConversation;
|
|
}
|
|
|
|
/**
|
|
* Set a current conversation
|
|
* @param url Custom URL to use for conversation endpoints
|
|
*/
|
|
static setCurrentConversation(
|
|
currentConversation: Conversation | null,
|
|
): void {
|
|
this.currentConversation = currentConversation;
|
|
}
|
|
|
|
/**
|
|
* Get the url for the conversation. If
|
|
*/
|
|
static getConversationUrl(conversationId: string): string {
|
|
if (this.currentConversation?.conversation_id === conversationId) {
|
|
if (this.currentConversation.url) {
|
|
return this.currentConversation.url;
|
|
}
|
|
}
|
|
return `/api/conversations/${conversationId}`;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the list of models available
|
|
* @returns List of models available
|
|
*/
|
|
static async getModels(): Promise<string[]> {
|
|
const { data } = await openHands.get<string[]>("/api/options/models");
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the list of agents available
|
|
* @returns List of agents available
|
|
*/
|
|
static async getAgents(): Promise<string[]> {
|
|
const { data } = await openHands.get<string[]>("/api/options/agents");
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the list of security analyzers available
|
|
* @returns List of security analyzers available
|
|
*/
|
|
static async getSecurityAnalyzers(): Promise<string[]> {
|
|
const { data } = await openHands.get<string[]>(
|
|
"/api/options/security-analyzers",
|
|
);
|
|
return data;
|
|
}
|
|
|
|
static async getConfig(): Promise<GetConfigResponse> {
|
|
const { data } = await openHands.get<GetConfigResponse>(
|
|
"/api/options/config",
|
|
);
|
|
return data;
|
|
}
|
|
|
|
static getConversationHeaders(): AxiosHeaders {
|
|
const headers = new AxiosHeaders();
|
|
const sessionApiKey = this.currentConversation?.session_api_key;
|
|
if (sessionApiKey) {
|
|
headers.set("X-Session-API-Key", sessionApiKey);
|
|
}
|
|
return headers;
|
|
}
|
|
|
|
/**
|
|
* Send feedback to the server
|
|
* @param data Feedback data
|
|
* @returns The stored feedback data
|
|
*/
|
|
static async submitFeedback(
|
|
conversationId: string,
|
|
feedback: Feedback,
|
|
): Promise<FeedbackResponse> {
|
|
const url = `/api/conversations/${conversationId}/submit-feedback`;
|
|
const { data } = await openHands.post<FeedbackResponse>(url, feedback);
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Submit conversation feedback with rating
|
|
* @param conversationId The conversation ID
|
|
* @param rating The rating (1-5)
|
|
* @param eventId Optional event ID this feedback corresponds to
|
|
* @param reason Optional reason for the rating
|
|
* @returns Response from the feedback endpoint
|
|
*/
|
|
static async submitConversationFeedback(
|
|
conversationId: string,
|
|
rating: number,
|
|
eventId?: number,
|
|
reason?: string,
|
|
): Promise<{ status: string; message: string }> {
|
|
const url = `/feedback/conversation`;
|
|
const payload = {
|
|
conversation_id: conversationId,
|
|
event_id: eventId,
|
|
rating,
|
|
reason,
|
|
metadata: { source: "likert-scale" },
|
|
};
|
|
const { data } = await openHands.post<{ status: string; message: string }>(
|
|
url,
|
|
payload,
|
|
);
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Check if feedback exists for a specific conversation and event
|
|
* @param conversationId The conversation ID
|
|
* @param eventId The event ID to check
|
|
* @returns Feedback data including existence, rating, and reason
|
|
*/
|
|
static async checkFeedbackExists(
|
|
conversationId: string,
|
|
eventId: number,
|
|
): Promise<{ exists: boolean; rating?: number; reason?: string }> {
|
|
try {
|
|
const url = `/feedback/conversation/${conversationId}/${eventId}`;
|
|
const { data } = await openHands.get<{
|
|
exists: boolean;
|
|
rating?: number;
|
|
reason?: string;
|
|
}>(url);
|
|
return data;
|
|
} catch (error) {
|
|
// Error checking if feedback exists
|
|
return { exists: false };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Authenticate with GitHub token
|
|
* @returns Response with authentication status and user info if successful
|
|
*/
|
|
static async authenticate(
|
|
appMode: GetConfigResponse["APP_MODE"],
|
|
): Promise<boolean> {
|
|
if (appMode === "oss") return true;
|
|
|
|
// Just make the request, if it succeeds (no exception thrown), return true
|
|
await openHands.post<AuthenticateResponse>("/api/authenticate");
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get the blob of the workspace zip
|
|
* @returns Blob of the workspace zip
|
|
*/
|
|
static async getWorkspaceZip(conversationId: string): Promise<Blob> {
|
|
const url = `${this.getConversationUrl(conversationId)}/zip-directory`;
|
|
const response = await openHands.get(url, {
|
|
responseType: "blob",
|
|
headers: this.getConversationHeaders(),
|
|
});
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Get the web hosts
|
|
* @returns Array of web hosts
|
|
*/
|
|
static async getWebHosts(conversationId: string): Promise<string[]> {
|
|
const url = `${this.getConversationUrl(conversationId)}/web-hosts`;
|
|
const response = await openHands.get(url, {
|
|
headers: this.getConversationHeaders(),
|
|
});
|
|
return Object.keys(response.data.hosts);
|
|
}
|
|
|
|
/**
|
|
* @param code Code provided by GitHub
|
|
* @returns GitHub access token
|
|
*/
|
|
static async getGitHubAccessToken(
|
|
code: string,
|
|
): Promise<GitHubAccessTokenResponse> {
|
|
const { data } = await openHands.post<GitHubAccessTokenResponse>(
|
|
"/api/keycloak/callback",
|
|
{
|
|
code,
|
|
},
|
|
);
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Get the VSCode URL
|
|
* @returns VSCode URL
|
|
*/
|
|
static async getVSCodeUrl(
|
|
conversationId: string,
|
|
): Promise<GetVSCodeUrlResponse> {
|
|
const url = `${this.getConversationUrl(conversationId)}/vscode-url`;
|
|
const { data } = await openHands.get<GetVSCodeUrlResponse>(url, {
|
|
headers: this.getConversationHeaders(),
|
|
});
|
|
return data;
|
|
}
|
|
|
|
static async getRuntimeId(
|
|
conversationId: string,
|
|
): Promise<{ runtime_id: string }> {
|
|
const url = `${this.getConversationUrl(conversationId)}/config`;
|
|
const { data } = await openHands.get<{ runtime_id: string }>(url, {
|
|
headers: this.getConversationHeaders(),
|
|
});
|
|
return data;
|
|
}
|
|
|
|
static async getUserConversations(): Promise<Conversation[]> {
|
|
const { data } = await openHands.get<ResultSet<Conversation>>(
|
|
"/api/conversations?limit=20",
|
|
);
|
|
return data.results;
|
|
}
|
|
|
|
static async deleteUserConversation(conversationId: string): Promise<void> {
|
|
await openHands.delete(`/api/conversations/${conversationId}`);
|
|
}
|
|
|
|
static async createConversation(
|
|
selectedRepository?: string,
|
|
git_provider?: Provider,
|
|
initialUserMsg?: string,
|
|
suggested_task?: SuggestedTask,
|
|
selected_branch?: string,
|
|
conversationInstructions?: string,
|
|
): Promise<Conversation> {
|
|
const body = {
|
|
repository: selectedRepository,
|
|
git_provider,
|
|
selected_branch,
|
|
initial_user_msg: initialUserMsg,
|
|
suggested_task,
|
|
conversation_instructions: conversationInstructions,
|
|
};
|
|
|
|
const { data } = await openHands.post<Conversation>(
|
|
"/api/conversations",
|
|
body,
|
|
);
|
|
|
|
return data;
|
|
}
|
|
|
|
static async getConversation(
|
|
conversationId: string,
|
|
): Promise<Conversation | null> {
|
|
const { data } = await openHands.get<Conversation | null>(
|
|
`/api/conversations/${conversationId}`,
|
|
);
|
|
|
|
return data;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static async stopConversation(
|
|
conversationId: string,
|
|
): Promise<Conversation | null> {
|
|
const { data } = await openHands.post<Conversation | null>(
|
|
`/api/conversations/${conversationId}/stop`,
|
|
);
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Get the settings from the server or use the default settings if not found
|
|
*/
|
|
static async getSettings(): Promise<ApiSettings> {
|
|
const { data } = await openHands.get<ApiSettings>("/api/settings");
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Save the settings to the server. Only valid settings are saved.
|
|
* @param settings - the settings to save
|
|
*/
|
|
static async saveSettings(
|
|
settings: Partial<PostApiSettings>,
|
|
): Promise<boolean> {
|
|
const data = await openHands.post("/api/settings", settings);
|
|
return data.status === 200;
|
|
}
|
|
|
|
static async createCheckoutSession(amount: number): Promise<string> {
|
|
const { data } = await openHands.post(
|
|
"/api/billing/create-checkout-session",
|
|
{
|
|
amount,
|
|
},
|
|
);
|
|
return data.redirect_url;
|
|
}
|
|
|
|
static async createBillingSessionResponse(): Promise<string> {
|
|
const { data } = await openHands.post(
|
|
"/api/billing/create-customer-setup-session",
|
|
);
|
|
return data.redirect_url;
|
|
}
|
|
|
|
static async getBalance(): Promise<string> {
|
|
const { data } = await openHands.get<{ credits: string }>(
|
|
"/api/billing/credits",
|
|
);
|
|
return data.credits;
|
|
}
|
|
|
|
static async getGitUser(): Promise<GitUser> {
|
|
const response = await openHands.get<GitUser>("/api/user/info");
|
|
|
|
const { data } = response;
|
|
|
|
const user: GitUser = {
|
|
id: data.id,
|
|
login: data.login,
|
|
avatar_url: data.avatar_url,
|
|
company: data.company,
|
|
name: data.name,
|
|
email: data.email,
|
|
};
|
|
|
|
return user;
|
|
}
|
|
|
|
static async searchGitRepositories(
|
|
query: string,
|
|
per_page = 5,
|
|
): Promise<GitRepository[]> {
|
|
const response = await openHands.get<GitRepository[]>(
|
|
"/api/user/search/repositories",
|
|
{
|
|
params: {
|
|
query,
|
|
per_page,
|
|
},
|
|
},
|
|
);
|
|
|
|
return response.data;
|
|
}
|
|
|
|
static async getTrajectory(
|
|
conversationId: string,
|
|
): Promise<GetTrajectoryResponse> {
|
|
const url = `${this.getConversationUrl(conversationId)}/trajectory`;
|
|
const { data } = await openHands.get<GetTrajectoryResponse>(url, {
|
|
headers: this.getConversationHeaders(),
|
|
});
|
|
return data;
|
|
}
|
|
|
|
static async logout(appMode: GetConfigResponse["APP_MODE"]): Promise<void> {
|
|
const endpoint =
|
|
appMode === "saas" ? "/api/logout" : "/api/unset-provider-tokens";
|
|
await openHands.post(endpoint);
|
|
}
|
|
|
|
static async getGitChanges(conversationId: string): Promise<GitChange[]> {
|
|
const url = `${this.getConversationUrl(conversationId)}/git/changes`;
|
|
const { data } = await openHands.get<GitChange[]>(url, {
|
|
headers: this.getConversationHeaders(),
|
|
});
|
|
return data;
|
|
}
|
|
|
|
static async getGitChangeDiff(
|
|
conversationId: string,
|
|
path: string,
|
|
): Promise<GitChangeDiff> {
|
|
const url = `${this.getConversationUrl(conversationId)}/git/diff`;
|
|
const { data } = await openHands.get<GitChangeDiff>(url, {
|
|
params: { path },
|
|
headers: this.getConversationHeaders(),
|
|
});
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Given a PAT, retrieves the repositories of the user
|
|
* @returns A list of repositories
|
|
*/
|
|
static async retrieveUserGitRepositories() {
|
|
const { data } = await openHands.get<GitRepository[]>(
|
|
"/api/user/repositories",
|
|
{
|
|
params: {
|
|
sort: "pushed",
|
|
},
|
|
},
|
|
);
|
|
|
|
return data;
|
|
}
|
|
|
|
static async getRepositoryBranches(repository: string): Promise<Branch[]> {
|
|
const { data } = await openHands.get<Branch[]>(
|
|
`/api/user/repository/branches?repository=${encodeURIComponent(repository)}`,
|
|
);
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Get the available microagents associated with a conversation
|
|
* @param conversationId The ID of the conversation
|
|
* @returns The available microagents associated with the conversation
|
|
*/
|
|
static async getMicroagents(
|
|
conversationId: string,
|
|
): Promise<GetMicroagentsResponse> {
|
|
const url = `${this.getConversationUrl(conversationId)}/microagents`;
|
|
const { data } = await openHands.get<GetMicroagentsResponse>(url, {
|
|
headers: this.getConversationHeaders(),
|
|
});
|
|
return data;
|
|
}
|
|
|
|
static async getMicroagentPrompt(
|
|
conversationId: string,
|
|
eventId: number,
|
|
): Promise<string> {
|
|
const { data } = await openHands.get<GetMicroagentPromptResponse>(
|
|
`/api/conversations/${conversationId}/remember_prompt`,
|
|
{
|
|
params: { event_id: eventId },
|
|
},
|
|
);
|
|
|
|
return data.prompt;
|
|
}
|
|
}
|
|
|
|
export default OpenHands;
|