Download src/api/cloud/organization-service.api.ts from SaylorTwift/openhands: direct link, hf CLI and curl.
- Browser
- Download file 4.92 kB
-
https://huggingface.co/SaylorTwift/openhands/resolve/main/src/api/cloud/organization-service.api.ts
- Command line
-
hf download hf://SaylorTwift/openhands/src/api/cloud/organization-service.api.ts
-
curl -L -o organization-service.api.ts https://huggingface.co/SaylorTwift/openhands/resolve/main/src/api/cloud/organization-service.api.ts
4.92 kB
| import { HttpError } from "@openhands/typescript-client"; | |
| import { getActiveBackend } from "../backend-registry/active-store"; | |
| import type { Backend } from "../backend-registry/types"; | |
| import { callCloudProxy } from "./proxy"; | |
| import type { | |
| CloudApiKeyMetadata, | |
| CloudOrganization, | |
| CloudOrganizationMember, | |
| CloudOrganizationsResponse, | |
| } from "./types"; | |
| interface OrganizationsResult { | |
| items: CloudOrganization[]; | |
| currentOrgId: string | null; | |
| } | |
| function normalizeResult( | |
| data: CloudOrganizationsResponse | undefined | null, | |
| ): OrganizationsResult { | |
| return { | |
| items: data?.items ?? [], | |
| currentOrgId: data?.current_org_id ?? null, | |
| }; | |
| } | |
| function resolveBackend(backend?: Backend): Backend { | |
| if (backend) return backend; | |
| const active = getActiveBackend().backend; | |
| if (active.kind !== "cloud") { | |
| throw new Error( | |
| "Cloud organization calls require a cloud backend. Active backend is local.", | |
| ); | |
| } | |
| return active; | |
| } | |
| /** | |
| * Fetch the org list for a cloud backend. With no argument, uses the active | |
| * cloud backend; pass `backend` explicitly to fetch for an inactive cloud | |
| * (used by the selector to flatten all cloud rows). | |
| * | |
| * Calls the cloud API directly with the backend's bearer token. | |
| */ | |
| export async function getCloudOrganizations( | |
| backend?: Backend, | |
| ): Promise<OrganizationsResult> { | |
| const target = resolveBackend(backend); | |
| const data = await callCloudProxy<CloudOrganizationsResponse>({ | |
| backend: target, | |
| method: "GET", | |
| path: "/api/organizations", | |
| }); | |
| return normalizeResult(data); | |
| } | |
| /** | |
| * Fetch metadata for the API key used to authenticate this cloud backend. | |
| * The returned `orgId` is the single org the key is authorized to act on | |
| * (the cloud contract: one key → one org). | |
| * | |
| * Legacy keys minted before per-key org binding existed cause the upstream | |
| * to return HTTP 400 — we surface that as `isLegacyKey: true` with a null | |
| * `orgId` so the caller can fall back to the unfiltered behavior. Other | |
| * statuses (401 revoked, 5xx outage) propagate so React Query can mark | |
| * the query as failed and the selector can render the no-org-known row. | |
| */ | |
| export async function getCurrentCloudApiKey( | |
| backend?: Backend, | |
| ): Promise<{ orgId: string | null; isLegacyKey: boolean }> { | |
| const target = resolveBackend(backend); | |
| try { | |
| const data = await callCloudProxy<CloudApiKeyMetadata>({ | |
| backend: target, | |
| method: "GET", | |
| path: "/api/keys/current", | |
| }); | |
| return { orgId: data?.org_id ?? null, isLegacyKey: false }; | |
| } catch (e) { | |
| if (e instanceof HttpError && e.status === 400) { | |
| return { orgId: null, isLegacyKey: true }; | |
| } | |
| throw e; | |
| } | |
| } | |
| /** | |
| * Fetch `GET /api/organizations/{orgId}/me`. Identifies the calling user as | |
| * a member of `orgId`. The GUI uses `me.org_id === me.user_id` to decide | |
| * whether `orgId` is the user's personal workspace — that's the cloud | |
| * contract (the auto-generated personal-workspace org has the same id as | |
| * the user). | |
| * | |
| * `role` is the caller's role in the org (`owner` | `admin` | `member`, or | |
| * `null` if the upstream omits it). `permissions` is the server-defined | |
| * permission set for that role (e.g. `edit_org_settings`); it is `null` on | |
| * older app-servers that don't return it, so callers fall back to the role. | |
| * See `useCanManageOrgProfiles`. | |
| */ | |
| export async function getCloudOrganizationMe( | |
| orgId: string, | |
| backend?: Backend, | |
| ): Promise<{ | |
| orgId: string; | |
| userId: string; | |
| role: string | null; | |
| permissions?: string[] | null; | |
| }> { | |
| const target = resolveBackend(backend); | |
| const data = await callCloudProxy<{ | |
| org_id: string; | |
| user_id: string; | |
| role?: string; | |
| permissions?: string[]; | |
| }>({ | |
| backend: target, | |
| method: "GET", | |
| path: `/api/organizations/${encodeURIComponent(orgId)}/me`, | |
| }); | |
| return { | |
| orgId: data?.org_id ?? orgId, | |
| userId: data?.user_id ?? "", | |
| role: data?.role ?? null, | |
| // `null` when the field is absent (older app-server) so callers can fall | |
| // back to a role check; a present array is the server's source of truth. | |
| permissions: Array.isArray(data?.permissions) ? data.permissions : null, | |
| }; | |
| } | |
| /** | |
| * Fetch `GET /api/organizations/{orgId}/members/{userId}`: one member of | |
| * `orgId` by user id. Any org member may read it (same permission as the | |
| * members list). The upstream responds 404 when `userId` is not (or no | |
| * longer) a member of the org, and older app-servers that predate the route | |
| * 404 as well, so callers should treat a failure as "identity unresolved". | |
| */ | |
| export async function getCloudOrganizationMember( | |
| orgId: string, | |
| userId: string, | |
| backend?: Backend, | |
| ): Promise<CloudOrganizationMember> { | |
| const target = resolveBackend(backend); | |
| return callCloudProxy<CloudOrganizationMember>({ | |
| backend: target, | |
| method: "GET", | |
| path: `/api/organizations/${encodeURIComponent(orgId)}/members/${encodeURIComponent(userId)}`, | |
| }); | |
| } | |