File size: 3,328 Bytes
3d700dd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | import type {
ProviderConnection,
CreateProviderConnectionRequest,
UpdateProviderConnectionRequest,
} from "../provider-connections-service/provider-connections-service.api";
import { getActiveBackend } from "../backend-registry/active-store";
import type { Backend } from "../backend-registry/types";
import { callCloudProxy } from "./proxy";
/**
* Cloud provider-connections service.
*
* Provider-connection CRUD is routed to the org-scoped endpoints
* `/api/organizations/{orgId}/provider-connections`, which enforce
* `EDIT_ORG_SETTINGS` server-side (listing requires `VIEW_ORG_SETTINGS`) — so a
* member's mutation is rejected with 403 even on a direct API call, not just
* hidden in the UI. This mirrors the cloud LLM-profile service.
*
* Unlike the local agent-server (which returns a bare array), the org list
* route wraps results as `{ connections: [...] }`; this service unwraps that so
* callers see the same `ProviderConnection[]` shape on both backends. Individual
* mutations return a bare `ProviderConnection` on both backends. Neither route
* exposes the stored key; each item carries `api_key_set` instead.
*
* Cloud has no unbound-org fallback here: provider connections are inherently
* org-scoped, so an org must be bound. A cloud backend without an org id is a
* programming error rather than a legacy-key case.
*/
interface CloudProviderConnectionListResponse {
connections: ProviderConnection[];
}
/** Resolve the backend + org-scoped base path for the active cloud backend. */
function cloudProviderConnectionsTarget(): { backend: Backend; base: string } {
const { backend, orgId } = getActiveBackend();
if (backend.kind !== "cloud") {
throw new Error(
"Cloud provider-connections call requires a cloud backend.",
);
}
if (!orgId) {
throw new Error(
"Cloud provider connections require an organization-bound backend.",
);
}
return {
backend,
base: `/api/organizations/${encodeURIComponent(orgId)}/provider-connections`,
};
}
export async function fetchCloudProviderConnections(): Promise<
ProviderConnection[]
> {
const { backend, base } = cloudProviderConnectionsTarget();
const result = await callCloudProxy<CloudProviderConnectionListResponse>({
backend,
method: "GET",
path: base,
});
return result.connections ?? [];
}
export async function createCloudProviderConnection(
request: CreateProviderConnectionRequest,
): Promise<ProviderConnection> {
const { backend, base } = cloudProviderConnectionsTarget();
return callCloudProxy<ProviderConnection>({
backend,
method: "POST",
path: base,
body: request,
});
}
export async function updateCloudProviderConnection(
id: string,
request: UpdateProviderConnectionRequest,
): Promise<ProviderConnection> {
const { backend, base } = cloudProviderConnectionsTarget();
return callCloudProxy<ProviderConnection>({
backend,
method: "PATCH",
path: `${base}/${encodeURIComponent(id)}`,
body: request,
});
}
export async function deleteCloudProviderConnection(
id: string,
): Promise<ProviderConnection> {
const { backend, base } = cloudProviderConnectionsTarget();
return callCloudProxy<ProviderConnection>({
backend,
method: "DELETE",
path: `${base}/${encodeURIComponent(id)}`,
});
}
|