From b477db17020e449f78dc277c0095aa91b18b1c46 Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Mon, 26 Aug 2024 17:16:50 +0300 Subject: [PATCH] Add support for Project Users and Service Accounts --- README.md | 8 +- .../CreateProjectServiceAccountRequest.java | 25 ++++ .../jvm/openai/CreateProjectUserRequest.java | 34 +++++ .../jvm/openai/ModifyProjectUserRequest.java | 25 ++++ .../stefanbratanov/jvm/openai/OpenAI.java | 24 +++ .../jvm/openai/ProjectServiceAccount.java | 4 + .../openai/ProjectServiceAccountsClient.java | 141 ++++++++++++++++++ .../jvm/openai/ProjectUser.java | 4 + .../jvm/openai/ProjectUsersClient.java | 139 +++++++++++++++++ .../openai/OpenAIAdminIntegrationTest.java | 62 ++++++++ .../OpenApiSpecificationValidationTest.java | 39 +++++ .../jvm/openai/TestDataUtil.java | 38 +++++ 12 files changed, 539 insertions(+), 4 deletions(-) create mode 100644 src/main/java/io/github/stefanbratanov/jvm/openai/CreateProjectServiceAccountRequest.java create mode 100644 src/main/java/io/github/stefanbratanov/jvm/openai/CreateProjectUserRequest.java create mode 100644 src/main/java/io/github/stefanbratanov/jvm/openai/ModifyProjectUserRequest.java create mode 100644 src/main/java/io/github/stefanbratanov/jvm/openai/ProjectServiceAccount.java create mode 100644 src/main/java/io/github/stefanbratanov/jvm/openai/ProjectServiceAccountsClient.java create mode 100644 src/main/java/io/github/stefanbratanov/jvm/openai/ProjectUser.java create mode 100644 src/main/java/io/github/stefanbratanov/jvm/openai/ProjectUsersClient.java diff --git a/README.md b/README.md index 148a0d1..4523e19 100644 --- a/README.md +++ b/README.md @@ -76,10 +76,10 @@ ChatCompletion chatCompletion = chatClient.createChatCompletion(createChatComple | API | Status | |----------------------------------------------------------------------------------|:------:| | [Invites](https://platform.openai.com/docs/api-reference/invite) | ✔️ | -| [Users](https://platform.openai.com/docs/api-reference/users) | ✔️ | -| [Projects](https://platform.openai.com/docs/api-reference/projects) | ✔️ | -| [Project Users](https://platform.openai.com/docs/api-reference/project-users) | | -| [Project Service Accounts](https://platform.openai.com/docs/api-reference/project-service-accounts) | | +| [Users](https://platform.openai.com/docs/api-reference/users) | ✔️ | +| [Projects](https://platform.openai.com/docs/api-reference/projects) | ✔️ | +| [Project Users](https://platform.openai.com/docs/api-reference/project-users) | ✔️ | +| [Project Service Accounts](https://platform.openai.com/docs/api-reference/project-service-accounts) | ✔️ | | [Project API Keys](https://platform.openai.com/docs/api-reference/project-api-keys) | | | [Audit Logs](https://platform.openai.com/docs/api-reference/audit-logs) | | diff --git a/src/main/java/io/github/stefanbratanov/jvm/openai/CreateProjectServiceAccountRequest.java b/src/main/java/io/github/stefanbratanov/jvm/openai/CreateProjectServiceAccountRequest.java new file mode 100644 index 0000000..e34f9f1 --- /dev/null +++ b/src/main/java/io/github/stefanbratanov/jvm/openai/CreateProjectServiceAccountRequest.java @@ -0,0 +1,25 @@ +package io.github.stefanbratanov.jvm.openai; + +public record CreateProjectServiceAccountRequest(String name) { + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + + private String name; + + /** + * @param name The name of the service account being created. + */ + public Builder name(String name) { + this.name = name; + return this; + } + + public CreateProjectServiceAccountRequest build() { + return new CreateProjectServiceAccountRequest(name); + } + } +} diff --git a/src/main/java/io/github/stefanbratanov/jvm/openai/CreateProjectUserRequest.java b/src/main/java/io/github/stefanbratanov/jvm/openai/CreateProjectUserRequest.java new file mode 100644 index 0000000..a403e81 --- /dev/null +++ b/src/main/java/io/github/stefanbratanov/jvm/openai/CreateProjectUserRequest.java @@ -0,0 +1,34 @@ +package io.github.stefanbratanov.jvm.openai; + +public record CreateProjectUserRequest(String userId, String role) { + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + + private String userId; + private String role; + + /** + * @param userId The ID of the user. + */ + public Builder userId(String userId) { + this.userId = userId; + return this; + } + + /** + * @param role `owner` or `member`. + */ + public Builder role(String role) { + this.role = role; + return this; + } + + public CreateProjectUserRequest build() { + return new CreateProjectUserRequest(userId, role); + } + } +} diff --git a/src/main/java/io/github/stefanbratanov/jvm/openai/ModifyProjectUserRequest.java b/src/main/java/io/github/stefanbratanov/jvm/openai/ModifyProjectUserRequest.java new file mode 100644 index 0000000..9c68cd1 --- /dev/null +++ b/src/main/java/io/github/stefanbratanov/jvm/openai/ModifyProjectUserRequest.java @@ -0,0 +1,25 @@ +package io.github.stefanbratanov.jvm.openai; + +public record ModifyProjectUserRequest(String role) { + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + + private String role; + + /** + * @param role `owner` or `member` + */ + public Builder role(String role) { + this.role = role; + return this; + } + + public ModifyProjectUserRequest build() { + return new ModifyProjectUserRequest(role); + } + } +} diff --git a/src/main/java/io/github/stefanbratanov/jvm/openai/OpenAI.java b/src/main/java/io/github/stefanbratanov/jvm/openai/OpenAI.java index 0b183c5..3705987 100644 --- a/src/main/java/io/github/stefanbratanov/jvm/openai/OpenAI.java +++ b/src/main/java/io/github/stefanbratanov/jvm/openai/OpenAI.java @@ -35,6 +35,8 @@ public final class OpenAI { private final InvitesClient invitesClient; private final UsersClient usersClient; private final ProjectsClient projectsClient; + private final ProjectUsersClient projectUsersClient; + private final ProjectServiceAccountsClient projectServiceAccountsClient; private OpenAI( URI baseUrl, @@ -79,6 +81,11 @@ private OpenAI( usersClient = new UsersClient(baseUrl, adminAuthenticationHeaders, httpClient, requestTimeout); projectsClient = new ProjectsClient(baseUrl, adminAuthenticationHeaders, httpClient, requestTimeout); + projectUsersClient = + new ProjectUsersClient(baseUrl, adminAuthenticationHeaders, httpClient, requestTimeout); + projectServiceAccountsClient = + new ProjectServiceAccountsClient( + baseUrl, adminAuthenticationHeaders, httpClient, requestTimeout); } /** @@ -252,6 +259,23 @@ public ProjectsClient projectsClient() { return projectsClient; } + /** + * @return a client based on Project Users + */ + public ProjectUsersClient projectUsersClient() { + return projectUsersClient; + } + + /** + * @return a client based on Project + * Service Accounts + */ + public ProjectServiceAccountsClient projectServiceAccountsClient() { + return projectServiceAccountsClient; + } + private String[] createAuthenticationHeaders( Optional apiKey, Optional organization, Optional project) { List authHeaders = new ArrayList<>(); diff --git a/src/main/java/io/github/stefanbratanov/jvm/openai/ProjectServiceAccount.java b/src/main/java/io/github/stefanbratanov/jvm/openai/ProjectServiceAccount.java new file mode 100644 index 0000000..d0b8fe2 --- /dev/null +++ b/src/main/java/io/github/stefanbratanov/jvm/openai/ProjectServiceAccount.java @@ -0,0 +1,4 @@ +package io.github.stefanbratanov.jvm.openai; + +/** Represents an individual service account in a project. */ +public record ProjectServiceAccount(String id, String name, String role, long createdAt) {} diff --git a/src/main/java/io/github/stefanbratanov/jvm/openai/ProjectServiceAccountsClient.java b/src/main/java/io/github/stefanbratanov/jvm/openai/ProjectServiceAccountsClient.java new file mode 100644 index 0000000..fb426cb --- /dev/null +++ b/src/main/java/io/github/stefanbratanov/jvm/openai/ProjectServiceAccountsClient.java @@ -0,0 +1,141 @@ +package io.github.stefanbratanov.jvm.openai; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Manage service accounts within a project. A service account is a bot user that is not associated + * with a user. If a user leaves an organization, their keys and membership in projects will no + * longer work. Service accounts do not have this limitation. However, service accounts can also be + * deleted from a project. + * + *

Based on Project + * Service Accounts + */ +public final class ProjectServiceAccountsClient extends OpenAIClient { + + private static final String SERVICE_ACCOUNTS_SEGMENT = "/service_accounts"; + + private final URI baseUrl; + + ProjectServiceAccountsClient( + URI baseUrl, + String[] authenticationHeaders, + HttpClient httpClient, + Optional requestTimeout) { + super(authenticationHeaders, httpClient, requestTimeout); + this.baseUrl = baseUrl; + } + + /** + * Returns a list of service accounts in the project. + * + * @param after A cursor for use in pagination. after is an object ID that defines your place in + * the list. + * @param limit A limit on the number of objects to be returned. + * @throws OpenAIException in case of API errors + */ + public PaginatedProjectServiceAccounts listProjectServiceAccounts( + String projectId, Optional after, Optional limit) { + String queryParameters = + createQueryParameters( + Map.of(Constants.LIMIT_QUERY_PARAMETER, limit, Constants.AFTER_QUERY_PARAMETER, after)); + HttpRequest httpRequest = + newHttpRequestBuilder() + .uri( + baseUrl.resolve( + Endpoint.PROJECTS.getPath() + + "/" + + projectId + + SERVICE_ACCOUNTS_SEGMENT + + queryParameters)) + .GET() + .build(); + HttpResponse httpResponse = sendHttpRequest(httpRequest); + return deserializeResponse(httpResponse.body(), PaginatedProjectServiceAccounts.class); + } + + public record PaginatedProjectServiceAccounts( + List data, String firstId, String lastId, boolean hasMore) {} + + /** + * Creates a new service account in the project. This also returns an unredacted API key for the + * service account. + * + * @param projectId The ID of the project. + * @throws OpenAIException in case of API errors + */ + public ProjectServiceAccountCreateResponse createProjectServiceAccount( + String projectId, CreateProjectServiceAccountRequest request) { + HttpRequest httpRequest = + newHttpRequestBuilder(Constants.CONTENT_TYPE_HEADER, Constants.JSON_MEDIA_TYPE) + .uri( + baseUrl.resolve( + Endpoint.PROJECTS.getPath() + "/" + projectId + SERVICE_ACCOUNTS_SEGMENT)) + .POST(createBodyPublisher(request)) + .build(); + HttpResponse httpResponse = sendHttpRequest(httpRequest); + return deserializeResponse(httpResponse.body(), ProjectServiceAccountCreateResponse.class); + } + + public record ProjectServiceAccountCreateResponse( + String id, String name, String role, long createdAt, ApiKey apiKey) {} + + public record ApiKey(String id, String name, String value, long createdAt) {} + + /** + * Retrieves a service account in the project. + * + * @param projectId The ID of the project. + * @param serviceAccountId The ID of the service account. + * @throws OpenAIException in case of API errors + */ + public ProjectServiceAccount retrieveProjectServiceAccount( + String projectId, String serviceAccountId) { + HttpRequest httpRequest = + newHttpRequestBuilder() + .uri( + baseUrl.resolve( + Endpoint.PROJECTS.getPath() + + "/" + + projectId + + SERVICE_ACCOUNTS_SEGMENT + + "/" + + serviceAccountId)) + .GET() + .build(); + HttpResponse httpResponse = sendHttpRequest(httpRequest); + return deserializeResponse(httpResponse.body(), ProjectServiceAccount.class); + } + + /** + * Deletes a service account from the project. + * + * @param projectId The ID of the project. + * @param serviceAccountId The ID of the service account. + * @throws OpenAIException in case of API errors + */ + public DeletionStatus deleteProjectServiceAccount(String projectId, String serviceAccountId) { + HttpRequest httpRequest = + newHttpRequestBuilder() + .uri( + baseUrl.resolve( + Endpoint.PROJECTS.getPath() + + "/" + + projectId + + SERVICE_ACCOUNTS_SEGMENT + + "/" + + serviceAccountId)) + .DELETE() + .build(); + HttpResponse httpResponse = sendHttpRequest(httpRequest); + return deserializeResponse(httpResponse.body(), DeletionStatus.class); + } +} diff --git a/src/main/java/io/github/stefanbratanov/jvm/openai/ProjectUser.java b/src/main/java/io/github/stefanbratanov/jvm/openai/ProjectUser.java new file mode 100644 index 0000000..c2c3e90 --- /dev/null +++ b/src/main/java/io/github/stefanbratanov/jvm/openai/ProjectUser.java @@ -0,0 +1,4 @@ +package io.github.stefanbratanov.jvm.openai; + +/** Represents an individual user in a project. */ +public record ProjectUser(String id, String name, String email, String role, long addedAt) {} diff --git a/src/main/java/io/github/stefanbratanov/jvm/openai/ProjectUsersClient.java b/src/main/java/io/github/stefanbratanov/jvm/openai/ProjectUsersClient.java new file mode 100644 index 0000000..82e6e59 --- /dev/null +++ b/src/main/java/io/github/stefanbratanov/jvm/openai/ProjectUsersClient.java @@ -0,0 +1,139 @@ +package io.github.stefanbratanov.jvm.openai; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Manage users within a project, including adding, updating roles, and removing users. Users cannot + * be removed from the Default project, unless they are being removed from the organization. + * + *

Based on Project + * Users + */ +public final class ProjectUsersClient extends OpenAIClient { + + private static final String USERS_SEGMENT = "/users"; + + private final URI baseUrl; + + ProjectUsersClient( + URI baseUrl, + String[] authenticationHeaders, + HttpClient httpClient, + Optional requestTimeout) { + super(authenticationHeaders, httpClient, requestTimeout); + this.baseUrl = baseUrl; + } + + /** + * Returns a list of users in the project. + * + * @param after A cursor for use in pagination. after is an object ID that defines your place in + * the list. + * @param limit A limit on the number of objects to be returned. + * @throws OpenAIException in case of API errors + */ + public PaginatedProjectUsers listProjectUsers( + String projectId, Optional after, Optional limit) { + String queryParameters = + createQueryParameters( + Map.of(Constants.LIMIT_QUERY_PARAMETER, limit, Constants.AFTER_QUERY_PARAMETER, after)); + HttpRequest httpRequest = + newHttpRequestBuilder() + .uri( + baseUrl.resolve( + Endpoint.PROJECTS.getPath() + + "/" + + projectId + + USERS_SEGMENT + + queryParameters)) + .GET() + .build(); + HttpResponse httpResponse = sendHttpRequest(httpRequest); + return deserializeResponse(httpResponse.body(), PaginatedProjectUsers.class); + } + + public record PaginatedProjectUsers( + List data, String firstId, String lastId, boolean hasMore) {} + + /** + * Adds a user to the project. Users must already be members of the organization to be added to a + * project. + * + * @param projectId The ID of the project. + * @throws OpenAIException in case of API errors + */ + public ProjectUser createProjectUser(String projectId, CreateProjectUserRequest request) { + HttpRequest httpRequest = + newHttpRequestBuilder(Constants.CONTENT_TYPE_HEADER, Constants.JSON_MEDIA_TYPE) + .uri(baseUrl.resolve(Endpoint.PROJECTS.getPath() + "/" + projectId + USERS_SEGMENT)) + .POST(createBodyPublisher(request)) + .build(); + HttpResponse httpResponse = sendHttpRequest(httpRequest); + return deserializeResponse(httpResponse.body(), ProjectUser.class); + } + + /** + * Retrieves a user in the project. + * + * @param projectId The ID of the project. + * @param userId The ID of the user. + * @throws OpenAIException in case of API errors + */ + public ProjectUser retrieveProjectUser(String projectId, String userId) { + HttpRequest httpRequest = + newHttpRequestBuilder() + .uri( + baseUrl.resolve( + Endpoint.PROJECTS.getPath() + "/" + projectId + USERS_SEGMENT + "/" + userId)) + .GET() + .build(); + HttpResponse httpResponse = sendHttpRequest(httpRequest); + return deserializeResponse(httpResponse.body(), ProjectUser.class); + } + + /** + * Modifies a user's role in the project. + * + * @param projectId The ID of the project. + * @param userId The ID of the user. + * @throws OpenAIException in case of API errors + */ + public ProjectUser modifyProjectUser( + String projectId, String userId, ModifyProjectUserRequest request) { + HttpRequest httpRequest = + newHttpRequestBuilder(Constants.CONTENT_TYPE_HEADER, Constants.JSON_MEDIA_TYPE) + .uri( + baseUrl.resolve( + Endpoint.PROJECTS.getPath() + "/" + projectId + USERS_SEGMENT + "/" + userId)) + .POST(createBodyPublisher(request)) + .build(); + HttpResponse httpResponse = sendHttpRequest(httpRequest); + return deserializeResponse(httpResponse.body(), ProjectUser.class); + } + + /** + * Deletes a user from the project. + * + * @param projectId The ID of the project. + * @param userId The ID of the user. + * @throws OpenAIException in case of API errors + */ + public DeletionStatus deleteProjectUser(String projectId, String userId) { + HttpRequest httpRequest = + newHttpRequestBuilder() + .uri( + baseUrl.resolve( + Endpoint.PROJECTS.getPath() + "/" + projectId + USERS_SEGMENT + "/" + userId)) + .DELETE() + .build(); + HttpResponse httpResponse = sendHttpRequest(httpRequest); + return deserializeResponse(httpResponse.body(), DeletionStatus.class); + } +} diff --git a/src/test/java/io/github/stefanbratanov/jvm/openai/OpenAIAdminIntegrationTest.java b/src/test/java/io/github/stefanbratanov/jvm/openai/OpenAIAdminIntegrationTest.java index f46507e..86d8c6f 100644 --- a/src/test/java/io/github/stefanbratanov/jvm/openai/OpenAIAdminIntegrationTest.java +++ b/src/test/java/io/github/stefanbratanov/jvm/openai/OpenAIAdminIntegrationTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.github.stefanbratanov.jvm.openai.ProjectServiceAccountsClient.ProjectServiceAccountCreateResponse; import io.github.stefanbratanov.jvm.openai.UsersClient.PaginatedUsers; import java.time.Instant; import java.util.List; @@ -85,4 +86,65 @@ void testProjectsClient() { assertThat(retrievedProject).isEqualTo(project); } + + @Test + void testProjectUsersClient() { + ProjectUsersClient projectUsersClient = openAI.projectUsersClient(); + + Project project = retrieveProject(); + + List projectUsers = + projectUsersClient + .listProjectUsers(project.id(), Optional.empty(), Optional.empty()) + .data(); + + assertThat(projectUsers).isNotEmpty(); + + ProjectUser projectUser = projectUsers.get(0); + + ProjectUser retrievedProjectUser = + projectUsersClient.retrieveProjectUser(project.id(), projectUser.id()); + + assertThat(retrievedProjectUser).isEqualTo(projectUser); + } + + @Test + void testProjectServiceAccountsClient() { + ProjectServiceAccountsClient projectServiceAccountsClient = + openAI.projectServiceAccountsClient(); + + Project project = retrieveProject(); + + CreateProjectServiceAccountRequest createRequest = + CreateProjectServiceAccountRequest.newBuilder() + .name("foobar" + System.currentTimeMillis()) + .build(); + + ProjectServiceAccountCreateResponse createResponse = + projectServiceAccountsClient.createProjectServiceAccount(project.id(), createRequest); + + assertThat(createResponse.name()).isEqualTo(createRequest.name()); + assertThat(createResponse.apiKey()).isNotNull(); + + ProjectServiceAccount retrievedProjectServiceAccount = + projectServiceAccountsClient.retrieveProjectServiceAccount( + project.id(), createResponse.id()); + + assertThat(retrievedProjectServiceAccount.id()).isEqualTo(createResponse.id()); + assertThat(retrievedProjectServiceAccount.role()).isEqualTo(createResponse.role()); + + // cleanup + DeletionStatus deletionStatus = + projectServiceAccountsClient.deleteProjectServiceAccount(project.id(), createResponse.id()); + + assertThat(deletionStatus.deleted()).isTrue(); + } + + private Project retrieveProject() { + return openAI + .projectsClient() + .listProjects(Optional.empty(), Optional.empty(), Optional.empty()) + .data() + .get(0); + } } diff --git a/src/test/java/io/github/stefanbratanov/jvm/openai/OpenApiSpecificationValidationTest.java b/src/test/java/io/github/stefanbratanov/jvm/openai/OpenApiSpecificationValidationTest.java index 6f3172d..4672982 100644 --- a/src/test/java/io/github/stefanbratanov/jvm/openai/OpenApiSpecificationValidationTest.java +++ b/src/test/java/io/github/stefanbratanov/jvm/openai/OpenApiSpecificationValidationTest.java @@ -12,6 +12,7 @@ import com.atlassian.oai.validator.report.ValidationReport; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import io.github.stefanbratanov.jvm.openai.ProjectServiceAccountsClient.ProjectServiceAccountCreateResponse; import io.github.stefanbratanov.jvm.openai.RunStepsClient.PaginatedThreadRunSteps; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.parser.OpenAPIV3Parser; @@ -487,6 +488,44 @@ void validateProjects() { validate(request, response); } + @RepeatedTest(25) + void validateProjectUsers() { + CreateProjectUserRequest createProjectUserRequest = + testDataUtil.randomCreateProjectUserRequest(); + + Request request = + createRequestWithBody( + Method.POST, + "/" + Endpoint.PROJECTS.getPath() + "/{project_id}/users", + serializeObject(createProjectUserRequest)); + + ProjectUser projectUser = testDataUtil.randomProjectUser(); + + Response response = createResponseWithBody(serializeObject(projectUser)); + + validate(request, response); + } + + @RepeatedTest(25) + void validateProjectServiceAccounts() { + CreateProjectServiceAccountRequest createProjectServiceAccountRequest = + testDataUtil.randomCreateProjectServiceAccountRequest(); + + Request request = + createRequestWithBody( + Method.POST, + "/" + Endpoint.PROJECTS.getPath() + "/{project_id}/service_accounts", + serializeObject(createProjectServiceAccountRequest)); + + ProjectServiceAccountCreateResponse projectServiceAccountCreateResponse = + testDataUtil.randomProjectServiceAccountCreateResponse(); + + Response response = + createResponseWithBody(serializeObject(projectServiceAccountCreateResponse)); + + validate(request, response); + } + private void validate(Request request, Response response, String... reportMessagesToIgnore) { ValidationReport report = validator.validate(request, response); validateReport(report, reportMessagesToIgnore); diff --git a/src/test/java/io/github/stefanbratanov/jvm/openai/TestDataUtil.java b/src/test/java/io/github/stefanbratanov/jvm/openai/TestDataUtil.java index 3e8be3f..fe0fdcc 100644 --- a/src/test/java/io/github/stefanbratanov/jvm/openai/TestDataUtil.java +++ b/src/test/java/io/github/stefanbratanov/jvm/openai/TestDataUtil.java @@ -2,6 +2,8 @@ import io.github.stefanbratanov.jvm.openai.CreateChatCompletionRequest.StreamOptions; import io.github.stefanbratanov.jvm.openai.FineTuningJobIntegration.Wandb; +import io.github.stefanbratanov.jvm.openai.ProjectServiceAccountsClient.ApiKey; +import io.github.stefanbratanov.jvm.openai.ProjectServiceAccountsClient.ProjectServiceAccountCreateResponse; import io.github.stefanbratanov.jvm.openai.RunStepsClient.PaginatedThreadRunSteps; import io.github.stefanbratanov.jvm.openai.ThreadMessage.Content.ImageFileContent; import io.github.stefanbratanov.jvm.openai.ThreadMessage.Content.ImageUrlContent; @@ -758,6 +760,42 @@ public Project randomProject() { oneOf("active", "archived")); } + public CreateProjectUserRequest randomCreateProjectUserRequest() { + return CreateProjectUserRequest.newBuilder() + .userId(randomString(7)) + .role(oneOf("owner", "member")) + .build(); + } + + public ProjectUser randomProjectUser() { + return new ProjectUser( + randomString(5), + randomString(7), + "user@example.com", + oneOf("owner", "member"), + randomLong(99_999, 1_111_111)); + } + + public CreateProjectServiceAccountRequest randomCreateProjectServiceAccountRequest() { + return CreateProjectServiceAccountRequest.newBuilder().name(randomString(7)).build(); + } + + public ProjectServiceAccountCreateResponse randomProjectServiceAccountCreateResponse() { + ProjectServiceAccount projectServiceAccount = randomProjectServiceAccount(); + return new ProjectServiceAccountCreateResponse( + projectServiceAccount.id(), + projectServiceAccount.name(), + projectServiceAccount.role(), + projectServiceAccount.createdAt(), + new ApiKey( + randomString(5), randomString(7), randomString(12), randomLong(9999, 1_000_000))); + } + + private ProjectServiceAccount randomProjectServiceAccount() { + return new ProjectServiceAccount( + randomString(5), randomString(7), "member", randomLong(10_000, 99_999)); + } + private ChunkingStrategy.StaticChunkingStrategy randomStaticChunkingStrategy() { int randomMaxChunkSizeTokens = randomInt(100, 4096); return ChunkingStrategy.staticChunkingStrategy(