| import { isAxiosError } from "axios"; |
| import { RemoteWorkspace } from "@openhands/typescript-client/workspace/remote-workspace"; |
| import { mapAnyGitStatusToClientStatus } from "#/utils/git-status-mapper"; |
| import { buildHttpBaseUrl } from "#/utils/websocket-url"; |
| import type { |
| GitChange, |
| GitChangeDiff, |
| GitCommitsPage, |
| } from "../open-hands.types"; |
| import { getActiveBackend } from "../backend-registry/active-store"; |
| import { callCloudProxy } from "../cloud/proxy"; |
| import { getAgentServerClientOptions } from "../agent-server-client-options"; |
| import { isSdkHttpStatusError } from "../agent-server-compatibility"; |
|
|
| interface AgentServerGitChange { |
| status: string; |
| path: string; |
| } |
|
|
| interface AgentServerGitCommit { |
| sha: string; |
| short_sha: string; |
| subject: string; |
| author: string; |
| timestamp: string; |
| } |
|
|
| interface AgentServerGitCommitsPage { |
| commits: AgentServerGitCommit[]; |
| has_more: boolean; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| function toAbsoluteRuntimePath(path: string): string { |
| return path.startsWith("/") ? path : `/${path}`; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function getFromRuntime<T>( |
| conversationUrl: string | null | undefined, |
| sessionApiKey: string | null | undefined, |
| apiPath: string, |
| params: { path: string } & Record<string, string>, |
| ): Promise<T> { |
| const active = getActiveBackend().backend; |
|
|
| if (active.kind === "cloud" && conversationUrl) { |
| const search = new URLSearchParams({ |
| ...params, |
| path: toAbsoluteRuntimePath(params.path), |
| }); |
| return callCloudProxy<T>({ |
| backend: active, |
| method: "GET", |
| hostOverride: buildHttpBaseUrl(conversationUrl), |
| path: `${apiPath}?${search.toString()}`, |
| authMode: "session-api-key", |
| sessionApiKey: sessionApiKey ?? undefined, |
| }); |
| } |
|
|
| const response = await new RemoteWorkspace( |
| getAgentServerClientOptions({ conversationUrl, sessionApiKey }), |
| ).client.get<T>(apiPath, { params }); |
| return response.data; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function isEndpointMissingError(error: unknown): boolean { |
| return ( |
| isSdkHttpStatusError(error, 404) || |
| (isAxiosError(error) && error.response?.status === 404) |
| ); |
| } |
|
|
| class AgentServerGitService { |
| static async getGitChanges( |
| conversationId: string, |
| conversationUrl: string | null | undefined, |
| sessionApiKey: string | null | undefined, |
| path: string, |
| ): Promise<GitChange[]> { |
| const active = getActiveBackend().backend; |
|
|
| if (active.kind === "cloud" && conversationId) { |
| const params = new URLSearchParams(); |
| params.set("path", toAbsoluteRuntimePath(path)); |
| const data = await callCloudProxy<AgentServerGitChange[]>({ |
| backend: active, |
| method: "GET", |
| path: `/api/v1/app-conversations/${conversationId}/git/changes?${params.toString()}`, |
| }); |
| if (!Array.isArray(data)) { |
| throw new Error( |
| "Invalid response from runtime - runtime may be unavailable", |
| ); |
| } |
| return data.map((change) => ({ |
| status: mapAnyGitStatusToClientStatus( |
| String(change.status) as Parameters< |
| typeof mapAnyGitStatusToClientStatus |
| >[0], |
| ), |
| path: change.path, |
| })); |
| } |
|
|
| |
| |
| |
| |
| const changes = await new RemoteWorkspace( |
| getAgentServerClientOptions({ conversationUrl, sessionApiKey }), |
| ).gitChanges(path); |
|
|
| if (!Array.isArray(changes)) { |
| throw new Error( |
| "Invalid response from runtime - runtime may be unavailable", |
| ); |
| } |
|
|
| return changes.map((change) => ({ |
| status: mapAnyGitStatusToClientStatus( |
| String(change.status) as Parameters< |
| typeof mapAnyGitStatusToClientStatus |
| >[0], |
| ), |
| path: change.path, |
| })); |
| } |
|
|
| |
| |
| |
| |
| |
| static async getGitCommits( |
| conversationUrl: string | null | undefined, |
| sessionApiKey: string | null | undefined, |
| path: string, |
| limit = 50, |
| ): Promise<GitCommitsPage | null> { |
| try { |
| const page = await getFromRuntime<AgentServerGitCommitsPage>( |
| conversationUrl, |
| sessionApiKey, |
| "/api/git/commits", |
| { path, limit: String(limit) }, |
| ); |
| return { |
| commits: (page?.commits ?? []).map((commit) => ({ |
| sha: commit.sha, |
| shortSha: commit.short_sha, |
| subject: commit.subject, |
| author: commit.author, |
| timestamp: commit.timestamp, |
| })), |
| hasMore: Boolean(page?.has_more), |
| }; |
| } catch (error) { |
| if (isEndpointMissingError(error)) return null; |
| throw error; |
| } |
| } |
|
|
| |
| static async getCommitChanges( |
| conversationUrl: string | null | undefined, |
| sessionApiKey: string | null | undefined, |
| path: string, |
| sha: string, |
| ): Promise<GitChange[]> { |
| const changes = await getFromRuntime<AgentServerGitChange[]>( |
| conversationUrl, |
| sessionApiKey, |
| `/api/git/commits/${encodeURIComponent(sha)}/changes`, |
| { path }, |
| ); |
|
|
| if (!Array.isArray(changes)) { |
| throw new Error( |
| "Invalid response from runtime - runtime may be unavailable", |
| ); |
| } |
|
|
| return changes.map((change) => ({ |
| status: mapAnyGitStatusToClientStatus( |
| String(change.status) as Parameters< |
| typeof mapAnyGitStatusToClientStatus |
| >[0], |
| ), |
| path: change.path, |
| })); |
| } |
|
|
| static async getGitChangeDiff( |
| conversationId: string, |
| conversationUrl: string | null | undefined, |
| sessionApiKey: string | null | undefined, |
| path: string, |
| commit?: string, |
| ): Promise<GitChangeDiff> { |
| if (commit) { |
| |
| |
| const commitDiff = await getFromRuntime< |
| GitChangeDiff & { diff?: string } |
| >(conversationUrl, sessionApiKey, "/api/git/diff", { path, commit }); |
| return { |
| modified: commitDiff?.modified ?? "", |
| original: commitDiff?.original ?? "", |
| ...(commitDiff?.diff ? { diff: commitDiff.diff } : {}), |
| } as GitChangeDiff; |
| } |
|
|
| const active = getActiveBackend().backend; |
|
|
| if (active.kind === "cloud" && conversationId) { |
| const params = new URLSearchParams(); |
| params.set("path", toAbsoluteRuntimePath(path)); |
| const diff = await callCloudProxy<GitChangeDiff & { diff?: string }>({ |
| backend: active, |
| method: "GET", |
| path: `/api/v1/app-conversations/${conversationId}/git/diff?${params.toString()}`, |
| }); |
| return { |
| modified: diff?.modified ?? "", |
| original: diff?.original ?? "", |
| ...(diff?.diff ? { diff: diff.diff } : {}), |
| } as GitChangeDiff; |
| } |
|
|
| |
| |
| const diff = (await new RemoteWorkspace( |
| getAgentServerClientOptions({ conversationUrl, sessionApiKey }), |
| ).gitDiff(path)) as GitChangeDiff & { diff?: string }; |
|
|
| return { |
| modified: diff.modified ?? "", |
| original: diff.original ?? "", |
| ...(diff.diff ? { diff: diff.diff } : {}), |
| } as GitChangeDiff; |
| } |
| } |
|
|
| export default AgentServerGitService; |
|
|