Download src/api/canvas-extensions-service.ts from SaylorTwift/openhands: direct link, hf CLI and curl.
- Browser
- Download file 5.91 kB
-
https://huggingface.co/SaylorTwift/openhands/resolve/main/src/api/canvas-extensions-service.ts
- Command line
-
hf download hf://SaylorTwift/openhands/src/api/canvas-extensions-service.ts
-
curl -L -o canvas-extensions-service.ts https://huggingface.co/SaylorTwift/openhands/resolve/main/src/api/canvas-extensions-service.ts
5.91 kB
| import { AgentServerClient } from "@openhands/typescript-client/clients"; | |
| import { getAgentServerClientOptions } from "#/api/agent-server-client-options"; | |
| import { isSdkHttpStatusError } from "#/api/agent-server-compatibility"; | |
| import { | |
| getActiveBackend, | |
| isNoBackend, | |
| } from "#/api/backend-registry/active-store"; | |
| import type { Backend } from "#/api/backend-registry/types"; | |
| import type { | |
| CanvasExtensionAgentServerRequest, | |
| InstallCanvasExtensionRequest, | |
| InstalledCanvasExtensionInfo, | |
| } from "#/types/canvas-extension"; | |
| const CANVAS_EXTENSIONS_BASE_PATH = "/api/canvas-extensions"; | |
| const REMOTE_EXTENSION_SOURCE_PATTERN = | |
| /^(?:github:|https?:\/\/|git:\/\/|file:\/\/|[\w.-]+@[\w.-]+:)/i; | |
| export type CanvasExtensionsUnsupportedReason = | |
| | "no-backend" | |
| | "cloud-backend" | |
| | "missing-api"; | |
| export class CanvasExtensionsUnsupportedError extends Error { | |
| readonly reason: CanvasExtensionsUnsupportedReason; | |
| constructor(reason: CanvasExtensionsUnsupportedReason) { | |
| const message = | |
| reason === "no-backend" | |
| ? "Add an Agent Server backend to use Apps." | |
| : reason === "cloud-backend" | |
| ? "Apps are not available on Cloud backends yet." | |
| : "This Agent Server does not support Apps yet. Upgrade the backend and try again."; | |
| super(message); | |
| this.name = "CanvasExtensionsUnsupportedError"; | |
| this.reason = reason; | |
| } | |
| } | |
| export function isCanvasExtensionsUnsupportedError( | |
| error: unknown, | |
| ): error is CanvasExtensionsUnsupportedError { | |
| return ( | |
| error instanceof CanvasExtensionsUnsupportedError || | |
| (typeof error === "object" && | |
| error !== null && | |
| "name" in error && | |
| error.name === "CanvasExtensionsUnsupportedError") | |
| ); | |
| } | |
| function requireSupportedBackend(): void { | |
| const { backend } = getActiveBackend(); | |
| if (isNoBackend(backend)) { | |
| throw new CanvasExtensionsUnsupportedError("no-backend"); | |
| } | |
| if (backend.kind === "cloud") { | |
| throw new CanvasExtensionsUnsupportedError("cloud-backend"); | |
| } | |
| } | |
| function getClient(): AgentServerClient { | |
| requireSupportedBackend(); | |
| return new AgentServerClient(getAgentServerClientOptions()); | |
| } | |
| function getClientForBackend(backend: Backend): AgentServerClient { | |
| if (isNoBackend(backend)) { | |
| throw new CanvasExtensionsUnsupportedError("no-backend"); | |
| } | |
| if (backend.kind === "cloud") { | |
| throw new CanvasExtensionsUnsupportedError("cloud-backend"); | |
| } | |
| return new AgentServerClient({ | |
| host: backend.host, | |
| ...(backend.apiKey ? { apiKey: backend.apiKey } : {}), | |
| timeout: 60000, | |
| }); | |
| } | |
| async function mapUnsupported<T>(operation: () => Promise<T>): Promise<T> { | |
| try { | |
| return await operation(); | |
| } catch (error) { | |
| if (isSdkHttpStatusError(error, 404)) { | |
| throw new CanvasExtensionsUnsupportedError("missing-api"); | |
| } | |
| throw error; | |
| } | |
| } | |
| function installedExtensionPath(name: string): string { | |
| return `${CANVAS_EXTENSIONS_BASE_PATH}/installed/${encodeURIComponent(name)}`; | |
| } | |
| function joinLocalExtensionPath(source: string, repoPath: string): string { | |
| const separator = source.includes("\\") && !source.includes("/") ? "\\" : "/"; | |
| const basePath = source.replace(/[\\/]+$/, ""); | |
| const childPath = repoPath.replace(/^[\\/]+/, ""); | |
| if (!childPath) return source; | |
| if (!basePath) return `${separator}${childPath}`; | |
| return `${basePath}${separator}${childPath}`; | |
| } | |
| function normalizeInstallRequest( | |
| request: InstallCanvasExtensionRequest, | |
| ): InstallCanvasExtensionRequest { | |
| const repoPath = request.repo_path?.trim(); | |
| if (!repoPath || REMOTE_EXTENSION_SOURCE_PATTERN.test(request.source)) { | |
| return request; | |
| } | |
| return { | |
| ...request, | |
| source: joinLocalExtensionPath(request.source, repoPath), | |
| repo_path: null, | |
| }; | |
| } | |
| class CanvasExtensionsService { | |
| static async listInstalled(): Promise<InstalledCanvasExtensionInfo[]> { | |
| const client = getClient(); | |
| const response = await mapUnsupported(() => | |
| client.get<{ canvas_extensions: InstalledCanvasExtensionInfo[] }>( | |
| `${CANVAS_EXTENSIONS_BASE_PATH}/installed`, | |
| ), | |
| ); | |
| return response.canvas_extensions ?? []; | |
| } | |
| static async install( | |
| request: InstallCanvasExtensionRequest, | |
| ): Promise<InstalledCanvasExtensionInfo> { | |
| const client = getClient(); | |
| return mapUnsupported(() => | |
| client.post<InstalledCanvasExtensionInfo>( | |
| `${CANVAS_EXTENSIONS_BASE_PATH}/install`, | |
| normalizeInstallRequest(request), | |
| ), | |
| ); | |
| } | |
| static async setEnabled( | |
| name: string, | |
| enabled: boolean, | |
| ): Promise<{ name: string; enabled: boolean }> { | |
| const client = getClient(); | |
| return mapUnsupported(() => | |
| client.patch<{ name: string; enabled: boolean }>( | |
| installedExtensionPath(name), | |
| { enabled }, | |
| ), | |
| ); | |
| } | |
| static async uninstall(name: string): Promise<{ message: string }> { | |
| const client = getClient(); | |
| return mapUnsupported(() => | |
| client.delete<{ message: string }>(installedExtensionPath(name)), | |
| ); | |
| } | |
| static async fetchBundle(name: string, backend?: Backend): Promise<string> { | |
| const client = backend ? getClientForBackend(backend) : getClient(); | |
| return mapUnsupported(() => | |
| client.get<string>(`${installedExtensionPath(name)}/bundle`, { | |
| responseType: "text", | |
| }), | |
| ); | |
| } | |
| static async requestAgentServer<T = unknown>( | |
| request: CanvasExtensionAgentServerRequest, | |
| backend?: Backend, | |
| ): Promise<T> { | |
| if (!request.path.startsWith("/") || request.path.startsWith("//")) { | |
| throw new Error( | |
| "Canvas Extension Agent Server requests must use a root-relative path.", | |
| ); | |
| } | |
| const client = backend ? getClientForBackend(backend) : getClient(); | |
| return client.request<T>({ | |
| method: request.method ?? "GET", | |
| path: request.path, | |
| body: request.body, | |
| headers: request.headers, | |
| }); | |
| } | |
| } | |
| export default CanvasExtensionsService; | |