From 2a1703d633b6f1ef888c84bb0f97a489707fdf2b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 9 Dec 2025 04:37:10 +0000
Subject: [PATCH 01/59] chore(internal): codegen related update
---
.github/workflows/ci.yml | 54 +-
.github/workflows/publish-sonatype.yml | 6 +-
.gitignore | 3 +-
.stats.yml | 2 +
README.md | 304 +-
SECURITY.md | 8 +-
arcade-java-client-okhttp/build.gradle.kts | 1 +
.../client/okhttp/ArcadeOkHttpClient.kt | 214 +-
.../client/okhttp/ArcadeOkHttpClientAsync.kt | 214 +-
.../dev/arcade/client/okhttp/OkHttpClient.kt | 114 +-
.../arcade/client/okhttp/OkHttpClientTest.kt | 44 +
arcade-java-core/build.gradle.kts | 27 +-
.../kotlin/dev/arcade/client/ArcadeClient.kt | 37 +
.../dev/arcade/client/ArcadeClientAsync.kt | 39 +
.../arcade/client/ArcadeClientAsyncImpl.kt | 59 +-
.../dev/arcade/client/ArcadeClientImpl.kt | 59 +-
.../main/kotlin/dev/arcade/core/AutoPager.kt | 21 +
.../kotlin/dev/arcade/core/AutoPagerAsync.kt | 88 +
.../dev/arcade/core/BaseDeserializer.kt | 31 +-
.../src/main/kotlin/dev/arcade/core/Check.kt | 67 +
.../kotlin/dev/arcade/core/ClientOptions.kt | 327 +-
.../kotlin/dev/arcade/core/DefaultSleeper.kt | 28 +
.../dev/arcade/core/HttpRequestBodies.kt | 108 -
.../kotlin/dev/arcade/core/ObjectMappers.kt | 152 +-
.../src/main/kotlin/dev/arcade/core/Page.kt | 33 +
.../main/kotlin/dev/arcade/core/PageAsync.kt | 35 +
.../core/PhantomReachableExecutorService.kt | 58 +
.../arcade/core/PhantomReachableSleeper.kt | 23 +
.../main/kotlin/dev/arcade/core/Properties.kt | 8 +-
.../kotlin/dev/arcade/core/RequestOptions.kt | 33 +-
.../main/kotlin/dev/arcade/core/Sleeper.kt | 21 +
.../main/kotlin/dev/arcade/core/Timeout.kt | 171 +
.../src/main/kotlin/dev/arcade/core/Utils.kt | 87 +
.../src/main/kotlin/dev/arcade/core/Values.kt | 407 +-
.../dev/arcade/core/handlers/ErrorHandler.kt | 151 +-
.../dev/arcade/core/handlers/JsonHandler.kt | 9 +-
.../arcade/core/http/AsyncStreamResponse.kt | 157 +
.../kotlin/dev/arcade/core/http/Headers.kt | 41 +-
.../kotlin/dev/arcade/core/http/HttpClient.kt | 9 +-
.../dev/arcade/core/http/HttpRequest.kt | 12 +-
.../dev/arcade/core/http/HttpRequestBodies.kt | 128 +
.../dev/arcade/core/http/HttpResponse.kt | 3 +-
.../dev/arcade/core/http/HttpResponseFor.kt | 25 +
...ntomReachableClosingAsyncStreamResponse.kt | 56 +
.../PhantomReachableClosingStreamResponse.kt | 21 +
.../dev/arcade/core/http/QueryParams.kt | 43 +
.../arcade/core/http/RetryingHttpClient.kt | 47 +-
.../dev/arcade/core/http/StreamResponse.kt | 19 +
.../kotlin/dev/arcade/errors/ArcadeError.kt | 76 -
.../arcade/errors/ArcadeRetryableException.kt | 14 +
.../arcade/errors/ArcadeServiceException.kt | 21 +-
.../dev/arcade/errors/BadRequestException.kt | 78 +-
.../arcade/errors/InternalServerException.kt | 89 +-
.../dev/arcade/errors/NotFoundException.kt | 74 +-
.../errors/PermissionDeniedException.kt | 78 +-
.../dev/arcade/errors/RateLimitException.kt | 78 +-
.../arcade/errors/UnauthorizedException.kt | 78 +-
.../errors/UnexpectedStatusCodeException.kt | 94 +-
.../errors/UnprocessableEntityException.kt | 78 +-
.../models/AdminAuthProviderListResponse.kt | 187 -
.../arcade/models/AdminSecretListResponse.kt | 184 -
.../models/AdminUserConnectionListPage.kt | 207 -
.../AdminUserConnectionListPageAsync.kt | 219 -
.../dev/arcade/models/AuthProviderResponse.kt | 3503 ---------
.../models/AuthProviderUpdateRequest.kt | 3240 ---------
.../kotlin/dev/arcade/models/AuthRequest.kt | 424 --
.../dev/arcade/models/AuthorizationContext.kt | 179 +-
.../arcade/models/AuthorizationResponse.kt | 319 +-
.../dev/arcade/models/AuthorizeToolRequest.kt | 174 -
.../kotlin/dev/arcade/models/ChatMessage.kt | 536 --
.../kotlin/dev/arcade/models/ChatRequest.kt | 911 ---
.../kotlin/dev/arcade/models/ChatResponse.kt | 218 -
.../main/kotlin/dev/arcade/models/Choice.kt | 221 -
.../dev/arcade/models/ConfirmUserRequest.kt | 128 -
.../dev/arcade/models/ConfirmUserResponse.kt | 129 -
.../dev/arcade/models/CreateWorkerRequest.kt | 800 ---
.../main/kotlin/dev/arcade/models/Error.kt | 124 +-
.../dev/arcade/models/ExecuteToolRequest.kt | 313 -
.../dev/arcade/models/SecretResponse.kt | 435 --
.../dev/arcade/models/ToolDefinition.kt | 1739 -----
.../kotlin/dev/arcade/models/ToolExecution.kt | 293 -
.../arcade/models/ToolFormattedGetResponse.kt | 91 -
.../arcade/models/ToolFormattedListPage.kt | 213 -
.../models/ToolFormattedListPageAsync.kt | 225 -
.../models/ToolFormattedListResponse.kt | 92 -
.../kotlin/dev/arcade/models/ToolListPage.kt | 203 -
.../dev/arcade/models/ToolListPageAsync.kt | 216 -
.../arcade/models/ToolScheduledGetResponse.kt | 417 --
.../arcade/models/ToolScheduledListPage.kt | 205 -
.../models/ToolScheduledListPageAsync.kt | 215 -
.../dev/arcade/models/UpdateWorkerRequest.kt | 742 --
.../main/kotlin/dev/arcade/models/Usage.kt | 147 -
.../arcade/models/UserConnectionResponse.kt | 267 -
.../kotlin/dev/arcade/models/ValueSchema.kt | 164 -
.../dev/arcade/models/WorkerHealthResponse.kt | 150 -
.../dev/arcade/models/WorkerListPage.kt | 203 -
.../dev/arcade/models/WorkerListPageAsync.kt | 216 -
.../dev/arcade/models/WorkerToolsPage.kt | 203 -
.../dev/arcade/models/WorkerToolsPageAsync.kt | 216 -
.../AuthProviderCreateParams.kt} | 64 +-
.../AuthProviderCreateRequest.kt | 6370 +++++++++++++++++
.../AuthProviderDeleteParams.kt} | 89 +-
.../authproviders/AuthProviderGetParams.kt} | 76 +-
.../authproviders/AuthProviderListParams.kt} | 44 +-
.../authproviders/AuthProviderListResponse.kt | 304 +
.../authproviders/AuthProviderPatchParams.kt} | 95 +-
.../authproviders/AuthProviderResponse.kt | 5865 +++++++++++++++
.../AuthProviderUpdateRequest.kt} | 4119 +++++++----
.../secrets/SecretCreateParams.kt} | 446 +-
.../secrets/SecretDeleteParams.kt} | 90 +-
.../secrets/SecretListParams.kt} | 44 +-
.../admin/secrets/SecretListResponse.kt | 302 +
.../models/admin/secrets/SecretResponse.kt | 720 ++
.../UserConnectionDeleteParams.kt} | 97 +-
.../userconnections/UserConnectionListPage.kt | 145 +
.../UserConnectionListPageAsync.kt | 160 +
.../UserConnectionListPageResponse.kt | 308 +
.../UserConnectionListParams.kt} | 165 +-
.../userconnections/UserConnectionResponse.kt | 469 ++
.../models/{ => auth}/AuthAuthorizeParams.kt | 46 +-
.../{ => auth}/AuthConfirmUserParams.kt | 46 +-
.../dev/arcade/models/auth/AuthRequest.kt | 673 ++
.../models/{ => auth}/AuthStatusParams.kt | 67 +-
.../arcade/models/auth/ConfirmUserRequest.kt | 204 +
.../arcade/models/auth/ConfirmUserResponse.kt | 203 +
.../dev/arcade/models/chat/ChatMessage.kt | 850 +++
.../dev/arcade/models/chat/ChatRequest.kt | 1405 ++++
.../dev/arcade/models/chat/ChatResponse.kt | 384 +
.../kotlin/dev/arcade/models/chat/Choice.kt | 363 +
.../kotlin/dev/arcade/models/chat/Usage.kt | 234 +
.../completions/CompletionCreateParams.kt} | 65 +-
.../models/{ => health}/HealthCheckParams.kt | 28 +-
.../models/{ => health}/HealthSchema.kt | 99 +-
.../models/tools/AuthorizeToolRequest.kt | 282 +
.../arcade/models/tools/ExecuteToolRequest.kt | 475 ++
.../models/{ => tools}/ExecuteToolResponse.kt | 1161 ++-
.../models/{ => tools}/ToolAuthorizeParams.kt | 46 +-
.../dev/arcade/models/tools/ToolDefinition.kt | 2851 ++++++++
.../models/{ => tools}/ToolExecuteParams.kt | 46 +-
.../dev/arcade/models/tools/ToolExecution.kt | 562 ++
.../{ => tools}/ToolExecutionAttempt.kt | 1077 ++-
.../models/{ => tools}/ToolGetParams.kt | 116 +-
.../dev/arcade/models/tools/ToolListPage.kt | 144 +
.../arcade/models/tools/ToolListPageAsync.kt | 159 +
.../models/tools/ToolListPageResponse.kt | 302 +
.../models/{ => tools}/ToolListParams.kt | 152 +-
.../dev/arcade/models/tools/ValueSchema.kt | 262 +
.../formatted/FormattedGetParams.kt} | 101 +-
.../tools/formatted/FormattedGetResponse.kt | 114 +
.../tools/formatted/FormattedListPage.kt | 145 +
.../tools/formatted/FormattedListPageAsync.kt | 160 +
.../formatted/FormattedListPageResponse.kt | 306 +
.../formatted/FormattedListParams.kt} | 143 +-
.../tools/formatted/FormattedListResponse.kt | 114 +
.../scheduled/ScheduledGetParams.kt} | 76 +-
.../tools/scheduled/ScheduledGetResponse.kt | 750 ++
.../tools/scheduled/ScheduledListPage.kt | 146 +
.../tools/scheduled/ScheduledListPageAsync.kt | 161 +
.../scheduled/ScheduledListPageResponse.kt | 305 +
.../scheduled/ScheduledListParams.kt} | 87 +-
.../models/workers/CreateWorkerRequest.kt | 1368 ++++
.../models/workers/UpdateWorkerRequest.kt | 1195 ++++
.../{ => workers}/WorkerCreateParams.kt | 46 +-
.../{ => workers}/WorkerDeleteParams.kt | 69 +-
.../models/{ => workers}/WorkerGetParams.kt | 60 +-
.../{ => workers}/WorkerHealthParams.kt | 60 +-
.../models/workers/WorkerHealthResponse.kt | 247 +
.../arcade/models/workers/WorkerListPage.kt | 144 +
.../models/workers/WorkerListPageAsync.kt | 159 +
.../models/workers/WorkerListPageResponse.kt | 302 +
.../models/{ => workers}/WorkerListParams.kt | 67 +-
.../models/{ => workers}/WorkerResponse.kt | 2045 ++++--
.../arcade/models/workers/WorkerToolsPage.kt | 146 +
.../models/workers/WorkerToolsPageAsync.kt | 161 +
.../models/workers/WorkerToolsPageResponse.kt | 303 +
.../models/{ => workers}/WorkerToolsParams.kt | 93 +-
.../{ => workers}/WorkerUpdateParams.kt | 73 +-
.../services/async/AdminServiceAsync.kt | 33 +
.../services/async/AdminServiceAsyncImpl.kt | 39 +
.../arcade/services/async/AuthServiceAsync.kt | 157 +-
.../services/async/AuthServiceAsyncImpl.kt | 210 +-
.../arcade/services/async/ChatServiceAsync.kt | 27 +
.../services/async/ChatServiceAsyncImpl.kt | 27 +
.../services/async/HealthServiceAsync.kt | 75 +-
.../services/async/HealthServiceAsyncImpl.kt | 84 +-
.../arcade/services/async/ToolServiceAsync.kt | 254 +-
.../services/async/ToolServiceAsyncImpl.kt | 298 +-
.../services/async/WorkerServiceAsync.kt | 451 +-
.../services/async/WorkerServiceAsyncImpl.kt | 469 +-
.../async/admin/AuthProviderServiceAsync.kt | 342 +-
.../admin/AuthProviderServiceAsyncImpl.kt | 348 +-
.../async/admin/SecretServiceAsync.kt | 199 +-
.../async/admin/SecretServiceAsyncImpl.kt | 209 +-
.../async/admin/UserConnectionServiceAsync.kt | 145 +-
.../admin/UserConnectionServiceAsyncImpl.kt | 154 +-
.../async/chat/CompletionServiceAsync.kt | 84 +-
.../async/chat/CompletionServiceAsyncImpl.kt | 90 +-
.../async/tools/FormattedServiceAsync.kt | 154 +-
.../async/tools/FormattedServiceAsyncImpl.kt | 168 +-
.../async/tools/ScheduledServiceAsync.kt | 154 +-
.../async/tools/ScheduledServiceAsyncImpl.kt | 165 +-
.../arcade/services/blocking/AdminService.kt | 31 +
.../services/blocking/AdminServiceImpl.kt | 39 +
.../arcade/services/blocking/AuthService.kt | 158 +-
.../services/blocking/AuthServiceImpl.kt | 192 +-
.../arcade/services/blocking/ChatService.kt | 27 +
.../services/blocking/ChatServiceImpl.kt | 27 +
.../arcade/services/blocking/HealthService.kt | 71 +-
.../services/blocking/HealthServiceImpl.kt | 82 +-
.../arcade/services/blocking/ToolService.kt | 250 +-
.../services/blocking/ToolServiceImpl.kt | 272 +-
.../arcade/services/blocking/WorkerService.kt | 436 +-
.../services/blocking/WorkerServiceImpl.kt | 422 +-
.../blocking/admin/AuthProviderService.kt | 336 +-
.../blocking/admin/AuthProviderServiceImpl.kt | 318 +-
.../services/blocking/admin/SecretService.kt | 188 +-
.../blocking/admin/SecretServiceImpl.kt | 190 +-
.../blocking/admin/UserConnectionService.kt | 145 +-
.../admin/UserConnectionServiceImpl.kt | 136 +-
.../blocking/chat/CompletionService.kt | 83 +-
.../blocking/chat/CompletionServiceImpl.kt | 84 +-
.../blocking/tools/FormattedService.kt | 150 +-
.../blocking/tools/FormattedServiceImpl.kt | 155 +-
.../blocking/tools/ScheduledService.kt | 147 +-
.../blocking/tools/ScheduledServiceImpl.kt | 152 +-
.../META-INF/proguard/arcade-java-core.pro | 32 +
.../dev/arcade/core/AutoPagerAsyncTest.kt | 182 +
.../kotlin/dev/arcade/core/AutoPagerTest.kt | 41 +
.../dev/arcade/core/ClientOptionsTest.kt | 34 +
.../dev/arcade/core/ObjectMappersTest.kt | 102 +
.../dev/arcade/core/PhantomReachableTest.kt | 2 +-
.../test/kotlin/dev/arcade/core/UtilsTest.kt | 33 +
.../core/http/AsyncStreamResponseTest.kt | 268 +
.../dev/arcade/core/http/HeadersTest.kt | 32 -
.../dev/arcade/core/http/QueryParamsTest.kt | 32 -
.../core/http/RetryingHttpClientTest.kt | 147 +-
.../dev/arcade/core/http/SerializerTest.kt | 99 -
.../AdminAuthProviderDeleteParamsTest.kt | 24 -
.../models/AdminAuthProviderGetParamsTest.kt | 24 -
.../models/AdminSecretDeleteParamsTest.kt | 24 -
.../models/AdminSecretListParamsTest.kt | 13 -
.../models/AdminSecretListResponseTest.kt | 60 -
.../AdminUserConnectionDeleteParamsTest.kt | 24 -
.../AdminUserConnectionListParamsTest.kt | 52 -
.../arcade/models/AuthorizationContextTest.kt | 30 +-
.../models/AuthorizationResponseTest.kt | 43 +-
.../arcade/models/AuthorizeToolRequestTest.kt | 25 -
.../arcade/models/ConfirmUserRequestTest.kt | 18 -
.../arcade/models/ConfirmUserResponseTest.kt | 18 -
.../kotlin/dev/arcade/models/ErrorTest.kt | 19 +-
.../dev/arcade/models/HealthSchemaTest.kt | 16 -
.../models/ToolFormattedGetParamsTest.kt | 42 -
.../models/ToolFormattedListParamsTest.kt | 50 -
.../models/ToolScheduledGetParamsTest.kt | 24 -
.../models/ToolScheduledListParamsTest.kt | 31 -
.../kotlin/dev/arcade/models/UsageTest.kt | 18 -
.../dev/arcade/models/ValueSchemaTest.kt | 23 -
.../arcade/models/WorkerHealthResponseTest.kt | 25 -
.../AuthProviderCreateParamsTest.kt} | 10 +-
.../AuthProviderCreateRequestTest.kt | 209 +-
.../AuthProviderDeleteParamsTest.kt | 23 +
.../AuthProviderGetParamsTest.kt | 23 +
.../AuthProviderListParamsTest.kt | 13 +
.../AuthProviderListResponseTest.kt} | 249 +-
.../AuthProviderPatchParamsTest.kt} | 49 +-
.../AuthProviderResponseTest.kt | 185 +-
.../AuthProviderUpdateRequestTest.kt | 172 +-
.../secrets/SecretCreateParamsTest.kt} | 34 +-
.../admin/secrets/SecretDeleteParamsTest.kt | 23 +
.../secrets/SecretListParamsTest.kt} | 6 +-
.../admin/secrets/SecretListResponseTest.kt | 100 +
.../{ => admin/secrets}/SecretResponseTest.kt | 39 +-
.../UserConnectionDeleteParamsTest.kt | 23 +
.../UserConnectionListPageResponseTest.kt | 90 +
.../UserConnectionListParamsTest.kt | 52 +
.../UserConnectionResponseTest.kt | 38 +-
.../{ => auth}/AuthAuthorizeParamsTest.kt | 4 +-
.../{ => auth}/AuthConfirmUserParamsTest.kt | 19 +-
.../models/{ => auth}/AuthRequestTest.kt | 38 +-
.../models/{ => auth}/AuthStatusParamsTest.kt | 20 +-
.../models/auth/ConfirmUserRequestTest.kt | 35 +
.../models/auth/ConfirmUserResponseTest.kt | 35 +
.../models/{ => chat}/ChatMessageTest.kt | 45 +-
.../models/{ => chat}/ChatRequestTest.kt | 78 +-
.../models/{ => chat}/ChatResponseTest.kt | 106 +-
.../arcade/models/{ => chat}/ChoiceTest.kt | 93 +-
.../dev/arcade/models/chat/UsageTest.kt | 31 +
.../CompletionCreateParamsTest.kt} | 12 +-
.../{ => health}/HealthCheckParamsTest.kt | 4 +-
.../arcade/models/health/HealthSchemaTest.kt | 32 +
.../models/tools/AuthorizeToolRequestTest.kt | 47 +
.../{ => tools}/ExecuteToolRequestTest.kt | 36 +-
.../{ => tools}/ExecuteToolResponseTest.kt | 85 +-
.../{ => tools}/ToolAuthorizeParamsTest.kt | 4 +-
.../models/{ => tools}/ToolDefinitionTest.kt | 104 +-
.../{ => tools}/ToolExecuteParamsTest.kt | 4 +-
.../{ => tools}/ToolExecutionAttemptTest.kt | 82 +-
.../models/{ => tools}/ToolExecutionTest.kt | 38 +-
.../models/{ => tools}/ToolGetParamsTest.kt | 41 +-
.../models/tools/ToolListPageResponseTest.kt | 302 +
.../models/{ => tools}/ToolListParamsTest.kt | 32 +-
.../arcade/models/tools/ValueSchemaTest.kt | 45 +
.../tools/formatted/FormattedGetParamsTest.kt | 46 +
.../formatted/FormattedGetResponseTest.kt | 30 +
.../FormattedListPageResponseTest.kt | 52 +
.../formatted/FormattedListParamsTest.kt | 58 +
.../formatted/FormattedListResponseTest.kt | 30 +
.../tools/scheduled/ScheduledGetParamsTest.kt | 23 +
.../scheduled/ScheduledGetResponseTest.kt} | 147 +-
.../ScheduledListPageResponseTest.kt | 98 +
.../scheduled/ScheduledListParamsTest.kt | 34 +
.../{ => workers}/CreateWorkerRequestTest.kt | 62 +-
.../{ => workers}/UpdateWorkerRequestTest.kt | 59 +-
.../{ => workers}/WorkerCreateParamsTest.kt | 4 +-
.../{ => workers}/WorkerDeleteParamsTest.kt | 13 +-
.../{ => workers}/WorkerGetParamsTest.kt | 13 +-
.../{ => workers}/WorkerHealthParamsTest.kt | 13 +-
.../workers/WorkerHealthResponseTest.kt | 47 +
.../workers/WorkerListPageResponseTest.kt | 328 +
.../{ => workers}/WorkerListParamsTest.kt | 19 +-
.../{ => workers}/WorkerResponseTest.kt | 113 +-
.../workers/WorkerToolsPageResponseTest.kt | 304 +
.../{ => workers}/WorkerToolsParamsTest.kt | 36 +-
.../{ => workers}/WorkerUpdateParamsTest.kt | 31 +-
.../dev/arcade/services/ErrorHandlingTest.kt | 836 ++-
.../dev/arcade/services/ServiceParamsTest.kt | 326 +-
.../services/async/AuthServiceAsyncTest.kt | 82 +
.../services/async/HealthServiceAsyncTest.kt | 27 +
.../services/async/ToolServiceAsyncTest.kt | 105 +
.../services/async/WorkerServiceAsyncTest.kt | 202 +
.../admin/AuthProviderServiceAsyncTest.kt | 472 ++
.../async/admin/SecretServiceAsyncTest.kt | 64 +
.../admin/UserConnectionServiceAsyncTest.kt | 41 +
.../async/chat/CompletionServiceAsyncTest.kt | 81 +
.../async/tools/FormattedServiceAsyncTest.kt | 46 +
.../async/tools/ScheduledServiceAsyncTest.kt | 42 +
.../services/blocking/AuthServiceTest.kt | 57 +-
.../services/blocking/HealthServiceTest.kt | 10 +-
.../services/blocking/ToolServiceTest.kt | 72 +-
.../services/blocking/WorkerServiceTest.kt | 130 +-
.../blocking/admin/AuthProviderServiceTest.kt | 443 +-
.../blocking/admin/SecretServiceTest.kt | 27 +-
.../admin/UserConnectionServiceTest.kt | 19 +-
.../blocking/chat/CompletionServiceTest.kt | 102 +-
.../blocking/tools/FormattedServiceTest.kt | 28 +-
.../blocking/tools/ScheduledServiceTest.kt | 24 +-
arcade-java-example/build.gradle.kts | 16 +-
arcade-java-proguard-test/build.gradle.kts | 101 +
.../proguard/ProGuardCompatibilityTest.kt | 77 +
arcade-java-proguard-test/test.pro | 9 +
arcade-java/build.gradle.kts | 21 +
bin/check-release-environment | 8 +-
build.gradle.kts | 29 +
buildSrc/build.gradle.kts | 5 +-
.../src/main/kotlin/arcade.java.gradle.kts | 99 +-
.../src/main/kotlin/arcade.kotlin.gradle.kts | 105 +-
gradle.properties | 17 +-
scripts/build | 8 +
scripts/fast-format | 46 +
scripts/format | 17 +-
scripts/java-format | 7 +
scripts/kotlin-format | 7 +
scripts/lint | 19 +-
scripts/mock | 4 +-
scripts/test | 4 +-
settings.gradle.kts | 16 +-
366 files changed, 56460 insertions(+), 26145 deletions(-)
create mode 100644 arcade-java-client-okhttp/src/test/kotlin/dev/arcade/client/okhttp/OkHttpClientTest.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/core/AutoPager.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/core/AutoPagerAsync.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/core/DefaultSleeper.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/core/HttpRequestBodies.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/core/Page.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/core/PageAsync.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/core/PhantomReachableExecutorService.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/core/PhantomReachableSleeper.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/core/Sleeper.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/core/Timeout.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/core/http/AsyncStreamResponse.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/core/http/HttpRequestBodies.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/core/http/HttpResponseFor.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/core/http/PhantomReachableClosingAsyncStreamResponse.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/core/http/PhantomReachableClosingStreamResponse.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/core/http/StreamResponse.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/errors/ArcadeError.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/errors/ArcadeRetryableException.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/AdminAuthProviderListResponse.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/AdminSecretListResponse.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/AdminUserConnectionListPage.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/AdminUserConnectionListPageAsync.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/AuthProviderResponse.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/AuthProviderUpdateRequest.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/AuthRequest.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/AuthorizeToolRequest.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ChatMessage.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ChatRequest.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ChatResponse.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/Choice.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ConfirmUserRequest.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ConfirmUserResponse.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/CreateWorkerRequest.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ExecuteToolRequest.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/SecretResponse.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ToolDefinition.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ToolExecution.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ToolFormattedGetResponse.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ToolFormattedListPage.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ToolFormattedListPageAsync.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ToolFormattedListResponse.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ToolListPage.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ToolListPageAsync.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ToolScheduledGetResponse.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ToolScheduledListPage.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ToolScheduledListPageAsync.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/UpdateWorkerRequest.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/Usage.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/UserConnectionResponse.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/ValueSchema.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/WorkerHealthResponse.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/WorkerListPage.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/WorkerListPageAsync.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/WorkerToolsPage.kt
delete mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/WorkerToolsPageAsync.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{AdminAuthProviderCreateParams.kt => admin/authproviders/AuthProviderCreateParams.kt} (74%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/admin/authproviders/AuthProviderCreateRequest.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{AdminAuthProviderDeleteParams.kt => admin/authproviders/AuthProviderDeleteParams.kt} (74%)
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{AdminAuthProviderGetParams.kt => admin/authproviders/AuthProviderGetParams.kt} (72%)
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{AdminAuthProviderListParams.kt => admin/authproviders/AuthProviderListParams.kt} (78%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/admin/authproviders/AuthProviderListResponse.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{AdminAuthProviderPatchParams.kt => admin/authproviders/AuthProviderPatchParams.kt} (68%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/admin/authproviders/AuthProviderResponse.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{AuthProviderCreateRequest.kt => admin/authproviders/AuthProviderUpdateRequest.kt} (55%)
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{AdminSecretCreateParams.kt => admin/secrets/SecretCreateParams.kt} (55%)
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{AdminSecretDeleteParams.kt => admin/secrets/SecretDeleteParams.kt} (73%)
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{AdminSecretListParams.kt => admin/secrets/SecretListParams.kt} (79%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/admin/secrets/SecretListResponse.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/admin/secrets/SecretResponse.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{AdminUserConnectionDeleteParams.kt => admin/userconnections/UserConnectionDeleteParams.kt} (73%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/admin/userconnections/UserConnectionListPage.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/admin/userconnections/UserConnectionListPageAsync.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/admin/userconnections/UserConnectionListPageResponse.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{AdminUserConnectionListParams.kt => admin/userconnections/UserConnectionListParams.kt} (72%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/admin/userconnections/UserConnectionResponse.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => auth}/AuthAuthorizeParams.kt (84%)
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => auth}/AuthConfirmUserParams.kt (84%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/auth/AuthRequest.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => auth}/AuthStatusParams.kt (79%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/auth/ConfirmUserRequest.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/auth/ConfirmUserResponse.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/chat/ChatMessage.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/chat/ChatRequest.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/chat/ChatResponse.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/chat/Choice.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/chat/Usage.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ChatCompletionCreateParams.kt => chat/completions/CompletionCreateParams.kt} (75%)
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => health}/HealthCheckParams.kt (87%)
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => health}/HealthSchema.kt (54%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/AuthorizeToolRequest.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/ExecuteToolRequest.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => tools}/ExecuteToolResponse.kt (50%)
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => tools}/ToolAuthorizeParams.kt (83%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/ToolDefinition.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => tools}/ToolExecuteParams.kt (84%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/ToolExecution.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => tools}/ToolExecutionAttempt.kt (51%)
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => tools}/ToolGetParams.kt (78%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/ToolListPage.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/ToolListPageAsync.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/ToolListPageResponse.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => tools}/ToolListParams.kt (75%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/ValueSchema.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ToolFormattedGetParams.kt => tools/formatted/FormattedGetParams.kt} (68%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/formatted/FormattedGetResponse.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/formatted/FormattedListPage.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/formatted/FormattedListPageAsync.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/formatted/FormattedListPageResponse.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ToolFormattedListParams.kt => tools/formatted/FormattedListParams.kt} (64%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/formatted/FormattedListResponse.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ToolScheduledGetParams.kt => tools/scheduled/ScheduledGetParams.kt} (73%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/scheduled/ScheduledGetResponse.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/scheduled/ScheduledListPage.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/scheduled/ScheduledListPageAsync.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/tools/scheduled/ScheduledListPageResponse.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ToolScheduledListParams.kt => tools/scheduled/ScheduledListParams.kt} (70%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/workers/CreateWorkerRequest.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/workers/UpdateWorkerRequest.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => workers}/WorkerCreateParams.kt (83%)
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => workers}/WorkerDeleteParams.kt (83%)
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => workers}/WorkerGetParams.kt (80%)
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => workers}/WorkerHealthParams.kt (80%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/workers/WorkerHealthResponse.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/workers/WorkerListPage.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/workers/WorkerListPageAsync.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/workers/WorkerListPageResponse.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => workers}/WorkerListParams.kt (78%)
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => workers}/WorkerResponse.kt (50%)
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/workers/WorkerToolsPage.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/workers/WorkerToolsPageAsync.kt
create mode 100644 arcade-java-core/src/main/kotlin/dev/arcade/models/workers/WorkerToolsPageResponse.kt
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => workers}/WorkerToolsParams.kt (74%)
rename arcade-java-core/src/main/kotlin/dev/arcade/models/{ => workers}/WorkerUpdateParams.kt (79%)
create mode 100644 arcade-java-core/src/main/resources/META-INF/proguard/arcade-java-core.pro
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/core/AutoPagerAsyncTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/core/AutoPagerTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/core/ClientOptionsTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/core/ObjectMappersTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/core/UtilsTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/core/http/AsyncStreamResponseTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/core/http/SerializerTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/AdminAuthProviderDeleteParamsTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/AdminAuthProviderGetParamsTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/AdminSecretDeleteParamsTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/AdminSecretListParamsTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/AdminSecretListResponseTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/AdminUserConnectionDeleteParamsTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/AdminUserConnectionListParamsTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/AuthorizeToolRequestTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/ConfirmUserRequestTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/ConfirmUserResponseTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/HealthSchemaTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/ToolFormattedGetParamsTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/ToolFormattedListParamsTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/ToolScheduledGetParamsTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/ToolScheduledListParamsTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/UsageTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/ValueSchemaTest.kt
delete mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/WorkerHealthResponseTest.kt
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{AdminAuthProviderCreateParamsTest.kt => admin/authproviders/AuthProviderCreateParamsTest.kt} (99%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => admin/authproviders}/AuthProviderCreateRequestTest.kt (64%)
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/admin/authproviders/AuthProviderDeleteParamsTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/admin/authproviders/AuthProviderGetParamsTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/admin/authproviders/AuthProviderListParamsTest.kt
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{AdminAuthProviderListResponseTest.kt => admin/authproviders/AuthProviderListResponseTest.kt} (62%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{AdminAuthProviderPatchParamsTest.kt => admin/authproviders/AuthProviderPatchParamsTest.kt} (97%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => admin/authproviders}/AuthProviderResponseTest.kt (64%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => admin/authproviders}/AuthProviderUpdateRequestTest.kt (64%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{AdminSecretCreateParamsTest.kt => admin/secrets/SecretCreateParamsTest.kt} (55%)
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/admin/secrets/SecretDeleteParamsTest.kt
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{AdminAuthProviderListParamsTest.kt => admin/secrets/SecretListParamsTest.kt} (51%)
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/admin/secrets/SecretListResponseTest.kt
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => admin/secrets}/SecretResponseTest.kt (56%)
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/admin/userconnections/UserConnectionDeleteParamsTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/admin/userconnections/UserConnectionListPageResponseTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/admin/userconnections/UserConnectionListParamsTest.kt
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => admin/userconnections}/UserConnectionResponseTest.kt (53%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => auth}/AuthAuthorizeParamsTest.kt (97%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => auth}/AuthConfirmUserParamsTest.kt (61%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => auth}/AuthRequestTest.kt (53%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => auth}/AuthStatusParamsTest.kt (57%)
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/auth/ConfirmUserRequestTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/auth/ConfirmUserResponseTest.kt
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => chat}/ChatMessageTest.kt (54%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => chat}/ChatRequestTest.kt (62%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => chat}/ChatResponseTest.kt (62%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => chat}/ChoiceTest.kt (61%)
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/chat/UsageTest.kt
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ChatCompletionCreateParamsTest.kt => chat/completions/CompletionCreateParamsTest.kt} (95%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => health}/HealthCheckParamsTest.kt (70%)
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/health/HealthSchemaTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/tools/AuthorizeToolRequestTest.kt
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => tools}/ExecuteToolRequestTest.kt (55%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => tools}/ExecuteToolResponseTest.kt (62%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => tools}/ToolAuthorizeParamsTest.kt (95%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => tools}/ToolDefinitionTest.kt (63%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => tools}/ToolExecuteParamsTest.kt (97%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => tools}/ToolExecutionAttemptTest.kt (62%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => tools}/ToolExecutionTest.kt (58%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => tools}/ToolGetParamsTest.kt (58%)
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/tools/ToolListPageResponseTest.kt
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => tools}/ToolListParamsTest.kt (58%)
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/tools/ValueSchemaTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/tools/formatted/FormattedGetParamsTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/tools/formatted/FormattedGetResponseTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/tools/formatted/FormattedListPageResponseTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/tools/formatted/FormattedListParamsTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/tools/formatted/FormattedListResponseTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/tools/scheduled/ScheduledGetParamsTest.kt
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ToolScheduledGetResponseTest.kt => tools/scheduled/ScheduledGetResponseTest.kt} (55%)
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/tools/scheduled/ScheduledListPageResponseTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/tools/scheduled/ScheduledListParamsTest.kt
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => workers}/CreateWorkerRequestTest.kt (60%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => workers}/UpdateWorkerRequestTest.kt (60%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => workers}/WorkerCreateParamsTest.kt (98%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => workers}/WorkerDeleteParamsTest.kt (57%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => workers}/WorkerGetParamsTest.kt (57%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => workers}/WorkerHealthParamsTest.kt (57%)
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/workers/WorkerHealthResponseTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/workers/WorkerListPageResponseTest.kt
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => workers}/WorkerListParamsTest.kt (58%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => workers}/WorkerResponseTest.kt (63%)
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/models/workers/WorkerToolsPageResponseTest.kt
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => workers}/WorkerToolsParamsTest.kt (54%)
rename arcade-java-core/src/test/kotlin/dev/arcade/models/{ => workers}/WorkerUpdateParamsTest.kt (95%)
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/services/async/AuthServiceAsyncTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/services/async/HealthServiceAsyncTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/services/async/ToolServiceAsyncTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/services/async/WorkerServiceAsyncTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/services/async/admin/AuthProviderServiceAsyncTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/services/async/admin/SecretServiceAsyncTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/services/async/admin/UserConnectionServiceAsyncTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/services/async/chat/CompletionServiceAsyncTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/services/async/tools/FormattedServiceAsyncTest.kt
create mode 100644 arcade-java-core/src/test/kotlin/dev/arcade/services/async/tools/ScheduledServiceAsyncTest.kt
create mode 100644 arcade-java-proguard-test/build.gradle.kts
create mode 100644 arcade-java-proguard-test/src/test/kotlin/dev/arcade/proguard/ProGuardCompatibilityTest.kt
create mode 100644 arcade-java-proguard-test/test.pro
create mode 100755 scripts/build
create mode 100755 scripts/fast-format
create mode 100755 scripts/java-format
create mode 100755 scripts/kotlin-format
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 121ace9..810f1c9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,18 +1,23 @@
name: CI
on:
push:
- branches:
- - main
+ branches-ignore:
+ - 'generated'
+ - 'codegen/**'
+ - 'integrated/**'
+ - 'stl-preview-head/**'
+ - 'stl-preview-base/**'
pull_request:
- branches:
- - main
- - next
+ branches-ignore:
+ - 'stl-preview-head/**'
+ - 'stl-preview-base/**'
jobs:
lint:
+ timeout-minutes: 15
name: lint
- runs-on: ubuntu-latest
-
+ runs-on: ${{ github.repository == 'stainless-sdks/arcade-engine-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- uses: actions/checkout@v4
@@ -23,7 +28,7 @@ jobs:
distribution: temurin
java-version: |
8
- 17
+ 21
cache: gradle
- name: Set up Gradle
@@ -31,10 +36,36 @@ jobs:
- name: Run lints
run: ./scripts/lint
+
+ build:
+ timeout-minutes: 15
+ name: build
+ runs-on: ${{ github.repository == 'stainless-sdks/arcade-engine-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: |
+ 8
+ 21
+ cache: gradle
+
+ - name: Set up Gradle
+ uses: gradle/actions/setup-gradle@v4
+
+ - name: Build SDK
+ run: ./scripts/build
+
test:
+ timeout-minutes: 15
name: test
- runs-on: ubuntu-latest
-
+ runs-on: ${{ github.repository == 'stainless-sdks/arcade-engine-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- uses: actions/checkout@v4
@@ -44,7 +75,7 @@ jobs:
distribution: temurin
java-version: |
8
- 17
+ 21
cache: gradle
- name: Set up Gradle
@@ -52,4 +83,3 @@ jobs:
- name: Run tests
run: ./scripts/test
-
diff --git a/.github/workflows/publish-sonatype.yml b/.github/workflows/publish-sonatype.yml
index d1dfa7b..fbcc81e 100644
--- a/.github/workflows/publish-sonatype.yml
+++ b/.github/workflows/publish-sonatype.yml
@@ -17,12 +17,12 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Java
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
distribution: temurin
java-version: |
8
- 17
+ 21
cache: gradle
- name: Set up Gradle
@@ -33,7 +33,7 @@ jobs:
export -- GPG_SIGNING_KEY_ID
printenv -- GPG_SIGNING_KEY | gpg --batch --passphrase-fd 3 --import 3<<< "$GPG_SIGNING_PASSWORD"
GPG_SIGNING_KEY_ID="$(gpg --with-colons --list-keys | awk -F : -- '/^pub:/ { getline; print "0x" substr($10, length($10) - 7) }')"
- ./gradlew publish
+ ./gradlew publish --no-configuration-cache
env:
SONATYPE_USERNAME: ${{ secrets.ARCADE_SONATYPE_USERNAME || secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.ARCADE_SONATYPE_PASSWORD || secrets.SONATYPE_PASSWORD }}
diff --git a/.gitignore b/.gitignore
index 39c31e3..b1346e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
.prism.log
.gradle
.idea
-build
+.kotlin
+build/
codegen.log
kls_database.db
diff --git a/.stats.yml b/.stats.yml
index e73b28d..7f6cedc 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,2 +1,4 @@
configured_endpoints: 30
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/arcade-ai%2Farcade-engine-4dc4e58ef402ce5362e0a8988b3928a8bfa0d5ba847f7ad8b14226a0cf282f28.yml
+openapi_spec_hash: fb72b9121306240c419669a3d42e45f6
+config_hash: b31a3f1bbe9abcc7bb144942d88ad1b6
diff --git a/README.md b/README.md
index 4cfa7a6..fa31e57 100644
--- a/README.md
+++ b/README.md
@@ -3,14 +3,19 @@
[](https://central.sonatype.com/artifact/dev.arcade/arcade-java/0.1.0-alpha.1)
+[](https://javadoc.io/doc/dev.arcade/arcade-java/0.1.0-alpha.1)
-The Arcade Java SDK provides convenient access to the Arcade REST API from applications written in Java.
+The Arcade Java SDK provides convenient access to the [Arcade REST API](https://docs.arcade.dev) from applications written in Java.
-It is generated with [Stainless](https://www.stainlessapi.com/).
+It is generated with [Stainless](https://www.stainless.com/).
-The REST API documentation can be found on [docs.arcade.dev](https://docs.arcade.dev).
+
+
+The REST API documentation can be found on [docs.arcade.dev](https://docs.arcade.dev). Javadocs are available on [javadoc.io](https://javadoc.io/doc/dev.arcade/arcade-java/0.1.0-alpha.1).
+
+
## Installation
@@ -26,9 +31,9 @@ implementation("dev.arcade:arcade-java:0.1.0-alpha.1")
```xml
- dev.arcade
- arcade-java
- 0.1.0-alpha.1
+ dev.arcade
+ arcade-java
+ 0.1.0-alpha.1
```
@@ -43,30 +48,30 @@ This library requires Java 8 or later.
```java
import dev.arcade.client.ArcadeClient;
import dev.arcade.client.okhttp.ArcadeOkHttpClient;
-import dev.arcade.models.ExecuteToolRequest;
-import dev.arcade.models.ExecuteToolResponse;
-import dev.arcade.models.ToolExecuteParams;
+import dev.arcade.models.tools.ExecuteToolRequest;
+import dev.arcade.models.tools.ExecuteToolResponse;
+import dev.arcade.models.tools.ToolExecuteParams;
-// Configures using the `ARCADE_API_KEY` environment variable
+// Configures using the `arcade.apiKey` and `arcade.baseUrl` system properties
+// Or configures using the `ARCADE_API_KEY` and `ARCADE_BASE_URL` environment variables
ArcadeClient client = ArcadeOkHttpClient.fromEnv();
-ToolExecuteParams params = ToolExecuteParams.builder()
- .executeToolRequest(ExecuteToolRequest.builder()
- .toolName("tool_name")
- .build())
+ExecuteToolRequest params = ExecuteToolRequest.builder()
+ .toolName("tool_name")
.build();
ExecuteToolResponse executeToolResponse = client.tools().execute(params);
```
## Client configuration
-Configure the client using environment variables:
+Configure the client using system properties or environment variables:
```java
import dev.arcade.client.ArcadeClient;
import dev.arcade.client.okhttp.ArcadeOkHttpClient;
-// Configures using the `ARCADE_API_KEY` environment variable
+// Configures using the `arcade.apiKey` and `arcade.baseUrl` system properties
+// Or configures using the `ARCADE_API_KEY` and `ARCADE_BASE_URL` environment variables
ArcadeClient client = ArcadeOkHttpClient.fromEnv();
```
@@ -88,7 +93,8 @@ import dev.arcade.client.ArcadeClient;
import dev.arcade.client.okhttp.ArcadeOkHttpClient;
ArcadeClient client = ArcadeOkHttpClient.builder()
- // Configures using the `ARCADE_API_KEY` environment variable
+ // Configures using the `arcade.apiKey` and `arcade.baseUrl` system properties
+ // Or configures using the `ARCADE_API_KEY` and `ARCADE_BASE_URL` environment variables
.fromEnv()
.apiKey("My API Key")
.build();
@@ -96,14 +102,32 @@ ArcadeClient client = ArcadeOkHttpClient.builder()
See this table for the available options:
-| Setter | Environment variable | Required | Default value |
-| -------- | -------------------- | -------- | ------------- |
-| `apiKey` | `ARCADE_API_KEY` | true | - |
+| Setter | System property | Environment variable | Required | Default value |
+| --------- | ---------------- | -------------------- | -------- | -------------------------- |
+| `apiKey` | `arcade.apiKey` | `ARCADE_API_KEY` | true | - |
+| `baseUrl` | `arcade.baseUrl` | `ARCADE_BASE_URL` | true | `"https://api.arcade.dev"` |
+
+System properties take precedence over environment variables.
> [!TIP]
> Don't create more than one client in the same application. Each client has a connection pool and
> thread pools, which are more efficient to share between requests.
+### Modifying configuration
+
+To temporarily use a modified client configuration, while reusing the same connection and thread pools, call `withOptions()` on any client or service:
+
+```java
+import dev.arcade.client.ArcadeClient;
+
+ArcadeClient clientWithOptions = client.withOptions(optionsBuilder -> {
+ optionsBuilder.baseUrl("https://example.com");
+ optionsBuilder.maxRetries(42);
+});
+```
+
+The `withOptions()` method does not affect the original client or service.
+
## Requests and responses
To send a request to the Arcade API, build an instance of some `Params` class and pass it to the corresponding client method. When the response is received, it will be deserialized into an instance of a Java class.
@@ -125,18 +149,17 @@ The default client is synchronous. To switch to asynchronous execution, call the
```java
import dev.arcade.client.ArcadeClient;
import dev.arcade.client.okhttp.ArcadeOkHttpClient;
-import dev.arcade.models.ExecuteToolRequest;
-import dev.arcade.models.ExecuteToolResponse;
-import dev.arcade.models.ToolExecuteParams;
+import dev.arcade.models.tools.ExecuteToolRequest;
+import dev.arcade.models.tools.ExecuteToolResponse;
+import dev.arcade.models.tools.ToolExecuteParams;
import java.util.concurrent.CompletableFuture;
-// Configures using the `ARCADE_API_KEY` environment variable
+// Configures using the `arcade.apiKey` and `arcade.baseUrl` system properties
+// Or configures using the `ARCADE_API_KEY` and `ARCADE_BASE_URL` environment variables
ArcadeClient client = ArcadeOkHttpClient.fromEnv();
-ToolExecuteParams params = ToolExecuteParams.builder()
- .executeToolRequest(ExecuteToolRequest.builder()
- .toolName("tool_name")
- .build())
+ExecuteToolRequest params = ExecuteToolRequest.builder()
+ .toolName("tool_name")
.build();
CompletableFuture executeToolResponse = client.async().tools().execute(params);
```
@@ -146,43 +169,72 @@ Or create an asynchronous client from the beginning:
```java
import dev.arcade.client.ArcadeClientAsync;
import dev.arcade.client.okhttp.ArcadeOkHttpClientAsync;
-import dev.arcade.models.ExecuteToolRequest;
-import dev.arcade.models.ExecuteToolResponse;
-import dev.arcade.models.ToolExecuteParams;
+import dev.arcade.models.tools.ExecuteToolRequest;
+import dev.arcade.models.tools.ExecuteToolResponse;
+import dev.arcade.models.tools.ToolExecuteParams;
import java.util.concurrent.CompletableFuture;
-// Configures using the `ARCADE_API_KEY` environment variable
+// Configures using the `arcade.apiKey` and `arcade.baseUrl` system properties
+// Or configures using the `ARCADE_API_KEY` and `ARCADE_BASE_URL` environment variables
ArcadeClientAsync client = ArcadeOkHttpClientAsync.fromEnv();
-ToolExecuteParams params = ToolExecuteParams.builder()
- .executeToolRequest(ExecuteToolRequest.builder()
- .toolName("tool_name")
- .build())
+ExecuteToolRequest params = ExecuteToolRequest.builder()
+ .toolName("tool_name")
.build();
CompletableFuture executeToolResponse = client.tools().execute(params);
```
The asynchronous client supports the same options as the synchronous one, except most methods return `CompletableFuture`s.
+## Raw responses
+
+The SDK defines methods that deserialize responses into instances of Java classes. However, these methods don't provide access to the response headers, status code, or the raw response body.
+
+To access this data, prefix any HTTP method call on a client or service with `withRawResponse()`:
+
+```java
+import dev.arcade.core.http.Headers;
+import dev.arcade.core.http.HttpResponseFor;
+import dev.arcade.models.chat.ChatRequest;
+import dev.arcade.models.chat.ChatResponse;
+import dev.arcade.models.chat.completions.CompletionCreateParams;
+
+ChatRequest params = ChatRequest.builder().build();
+HttpResponseFor chatResponse = client.chat().completions().withRawResponse().create(params);
+
+int statusCode = chatResponse.statusCode();
+Headers headers = chatResponse.headers();
+```
+
+You can still deserialize the response into an instance of a Java class if needed:
+
+```java
+import dev.arcade.models.chat.ChatResponse;
+
+ChatResponse parsedChatResponse = chatResponse.parse();
+```
+
## Error handling
The SDK throws custom unchecked exception types:
- [`ArcadeServiceException`](arcade-java-core/src/main/kotlin/dev/arcade/errors/ArcadeServiceException.kt): Base class for HTTP errors. See this table for which exception subclass is thrown for each HTTP status code:
- | Status | Exception |
- | ------ | ------------------------------- |
- | 400 | `BadRequestException` |
- | 401 | `AuthenticationException` |
- | 403 | `PermissionDeniedException` |
- | 404 | `NotFoundException` |
- | 422 | `UnprocessableEntityException` |
- | 429 | `RateLimitException` |
- | 5xx | `InternalServerException` |
- | others | `UnexpectedStatusCodeException` |
+ | Status | Exception |
+ | ------ | ---------------------------------------------------------------------------------------------------------------------- |
+ | 400 | [`BadRequestException`](arcade-java-core/src/main/kotlin/dev/arcade/errors/BadRequestException.kt) |
+ | 401 | [`UnauthorizedException`](arcade-java-core/src/main/kotlin/dev/arcade/errors/UnauthorizedException.kt) |
+ | 403 | [`PermissionDeniedException`](arcade-java-core/src/main/kotlin/dev/arcade/errors/PermissionDeniedException.kt) |
+ | 404 | [`NotFoundException`](arcade-java-core/src/main/kotlin/dev/arcade/errors/NotFoundException.kt) |
+ | 422 | [`UnprocessableEntityException`](arcade-java-core/src/main/kotlin/dev/arcade/errors/UnprocessableEntityException.kt) |
+ | 429 | [`RateLimitException`](arcade-java-core/src/main/kotlin/dev/arcade/errors/RateLimitException.kt) |
+ | 5xx | [`InternalServerException`](arcade-java-core/src/main/kotlin/dev/arcade/errors/InternalServerException.kt) |
+ | others | [`UnexpectedStatusCodeException`](arcade-java-core/src/main/kotlin/dev/arcade/errors/UnexpectedStatusCodeException.kt) |
- [`ArcadeIoException`](arcade-java-core/src/main/kotlin/dev/arcade/errors/ArcadeIoException.kt): I/O networking errors.
+- [`ArcadeRetryableException`](arcade-java-core/src/main/kotlin/dev/arcade/errors/ArcadeRetryableException.kt): Generic error indicating a failure that could be retried by the client.
+
- [`ArcadeInvalidDataException`](arcade-java-core/src/main/kotlin/dev/arcade/errors/ArcadeInvalidDataException.kt): Failure to interpret successfully parsed data. For example, when accessing a property that's supposed to be required, but the API unexpectedly omitted it from the response.
- [`ArcadeException`](arcade-java-core/src/main/kotlin/dev/arcade/errors/ArcadeException.kt): Base class for all exceptions. Most errors will result in one of the previously mentioned ones, but completely generic errors may be thrown using the base class.
@@ -194,20 +246,37 @@ The SDK uses the standard [OkHttp logging interceptor](https://github.com/square
Enable logging by setting the `ARCADE_LOG` environment variable to `info`:
```sh
-$ export ARCADE_LOG=info
+export ARCADE_LOG=info
```
Or to `debug` for more verbose logging:
```sh
-$ export ARCADE_LOG=debug
+export ARCADE_LOG=debug
```
+## ProGuard and R8
+
+Although the SDK uses reflection, it is still usable with [ProGuard](https://github.com/Guardsquare/proguard) and [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization) because `arcade-java-core` is published with a [configuration file](arcade-java-core/src/main/resources/META-INF/proguard/arcade-java-core.pro) containing [keep rules](https://www.guardsquare.com/manual/configuration/usage).
+
+ProGuard and R8 should automatically detect and use the published rules, but you can also manually copy the keep rules if necessary.
+
+## Jackson
+
+The SDK depends on [Jackson](https://github.com/FasterXML/jackson) for JSON serialization/deserialization. It is compatible with version 2.13.4 or higher, but depends on version 2.18.2 by default.
+
+The SDK throws an exception if it detects an incompatible Jackson version at runtime (e.g. if the default version was overridden in your Maven or Gradle config).
+
+If the SDK threw an exception, but you're _certain_ the version is compatible, then disable the version check using the `checkJacksonVersionCompatibility` on [`ArcadeOkHttpClient`](arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/ArcadeOkHttpClient.kt) or [`ArcadeOkHttpClientAsync`](arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/ArcadeOkHttpClientAsync.kt).
+
+> [!CAUTION]
+> We make no guarantee that the SDK works correctly when the Jackson version check is disabled.
+
## Network options
### Retries
-The SDK automatically retries 2 times by default, with a short exponential backoff.
+The SDK automatically retries 2 times by default, with a short exponential backoff between requests.
Only the following error types are retried:
@@ -217,7 +286,7 @@ Only the following error types are retried:
- 429 Rate Limit
- 5xx Internal
-The API may also explicitly instruct the SDK to retry or not retry a response.
+The API may also explicitly instruct the SDK to retry or not retry a request.
To set a custom number of retries, configure the client using the `maxRetries` method:
@@ -238,11 +307,9 @@ Requests time out after 1 minute by default.
To set a custom timeout, configure the method call using the `timeout` method:
```java
-import dev.arcade.models.ExecuteToolRequest;
-import dev.arcade.models.ExecuteToolResponse;
-import dev.arcade.models.ToolExecuteParams;
+import dev.arcade.models.chat.ChatResponse;
-ExecuteToolResponse executeToolResponse = client.tools().execute(
+ChatResponse chatResponse = client.chat().completions().create(
params, RequestOptions.builder().timeout(Duration.ofSeconds(30)).build()
);
```
@@ -280,6 +347,63 @@ ArcadeClient client = ArcadeOkHttpClient.builder()
.build();
```
+### HTTPS
+
+> [!NOTE]
+> Most applications should not call these methods, and instead use the system defaults. The defaults include
+> special optimizations that can be lost if the implementations are modified.
+
+To configure how HTTPS connections are secured, configure the client using the `sslSocketFactory`, `trustManager`, and `hostnameVerifier` methods:
+
+```java
+import dev.arcade.client.ArcadeClient;
+import dev.arcade.client.okhttp.ArcadeOkHttpClient;
+
+ArcadeClient client = ArcadeOkHttpClient.builder()
+ .fromEnv()
+ // If `sslSocketFactory` is set, then `trustManager` must be set, and vice versa.
+ .sslSocketFactory(yourSSLSocketFactory)
+ .trustManager(yourTrustManager)
+ .hostnameVerifier(yourHostnameVerifier)
+ .build();
+```
+
+### Custom HTTP client
+
+The SDK consists of three artifacts:
+
+- `arcade-java-core`
+ - Contains core SDK logic
+ - Does not depend on [OkHttp](https://square.github.io/okhttp)
+ - Exposes [`ArcadeClient`](arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClient.kt), [`ArcadeClientAsync`](arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientAsync.kt), [`ArcadeClientImpl`](arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientImpl.kt), and [`ArcadeClientAsyncImpl`](arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientAsyncImpl.kt), all of which can work with any HTTP client
+- `arcade-java-client-okhttp`
+ - Depends on [OkHttp](https://square.github.io/okhttp)
+ - Exposes [`ArcadeOkHttpClient`](arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/ArcadeOkHttpClient.kt) and [`ArcadeOkHttpClientAsync`](arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/ArcadeOkHttpClientAsync.kt), which provide a way to construct [`ArcadeClientImpl`](arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientImpl.kt) and [`ArcadeClientAsyncImpl`](arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientAsyncImpl.kt), respectively, using OkHttp
+- `arcade-java`
+ - Depends on and exposes the APIs of both `arcade-java-core` and `arcade-java-client-okhttp`
+ - Does not have its own logic
+
+This structure allows replacing the SDK's default HTTP client without pulling in unnecessary dependencies.
+
+#### Customized [`OkHttpClient`](https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.html)
+
+> [!TIP]
+> Try the available [network options](#network-options) before replacing the default client.
+
+To use a customized `OkHttpClient`:
+
+1. Replace your [`arcade-java` dependency](#installation) with `arcade-java-core`
+2. Copy `arcade-java-client-okhttp`'s [`OkHttpClient`](arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/OkHttpClient.kt) class into your code and customize it
+3. Construct [`ArcadeClientImpl`](arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientImpl.kt) or [`ArcadeClientAsyncImpl`](arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientAsyncImpl.kt), similarly to [`ArcadeOkHttpClient`](arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/ArcadeOkHttpClient.kt) or [`ArcadeOkHttpClientAsync`](arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/ArcadeOkHttpClientAsync.kt), using your customized client
+
+### Completely custom HTTP client
+
+To use a completely custom HTTP client:
+
+1. Replace your [`arcade-java` dependency](#installation) with `arcade-java-core`
+2. Write a class that implements the [`HttpClient`](arcade-java-core/src/main/kotlin/dev/arcade/core/http/HttpClient.kt) interface
+3. Construct [`ArcadeClientImpl`](arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientImpl.kt) or [`ArcadeClientAsyncImpl`](arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientAsyncImpl.kt), similarly to [`ArcadeOkHttpClient`](arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/ArcadeOkHttpClient.kt) or [`ArcadeOkHttpClientAsync`](arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/ArcadeOkHttpClientAsync.kt), using your new client class
+
## Undocumented API functionality
The SDK is typed for convenient usage of the documented API. However, it also supports working with undocumented or not yet supported parts of the API.
@@ -290,7 +414,7 @@ To set undocumented parameters, call the `putAdditionalHeader`, `putAdditionalQu
```java
import dev.arcade.core.JsonValue;
-import dev.arcade.models.ToolExecuteParams;
+import dev.arcade.models.tools.ToolExecuteParams;
ToolExecuteParams params = ToolExecuteParams.builder()
.putAdditionalHeader("Secret-Header", "42")
@@ -299,18 +423,74 @@ ToolExecuteParams params = ToolExecuteParams.builder()
.build();
```
-These can be accessed on the built object later using the `_additionalHeaders()`, `_additionalQueryParams()`, and `_additionalBodyProperties()` methods. You can also set undocumented parameters on nested headers, query params, or body classes using the `putAdditionalProperty` method. These properties can be accessed on the built object later using the `_additionalProperties()` method.
+These can be accessed on the built object later using the `_additionalHeaders()`, `_additionalQueryParams()`, and `_additionalBodyProperties()` methods.
+
+To set a documented parameter or property to an undocumented or not yet supported _value_, pass a [`JsonValue`](arcade-java-core/src/main/kotlin/dev/arcade/core/Values.kt) object to its setter:
+
+```java
+import dev.arcade.models.tools.ExecuteToolRequest;
+import dev.arcade.models.tools.ToolExecuteParams;
+
+ToolExecuteParams params = ToolExecuteParams.builder()
+ .executeToolRequest(ExecuteToolRequest.builder()
+ .toolName("tool_name")
+ .build())
+ .build();
+```
+
+The most straightforward way to create a [`JsonValue`](arcade-java-core/src/main/kotlin/dev/arcade/core/Values.kt) is using its `from(...)` method:
+
+```java
+import dev.arcade.core.JsonValue;
+import java.util.List;
+import java.util.Map;
+
+// Create primitive JSON values
+JsonValue nullValue = JsonValue.from(null);
+JsonValue booleanValue = JsonValue.from(true);
+JsonValue numberValue = JsonValue.from(42);
+JsonValue stringValue = JsonValue.from("Hello World!");
+
+// Create a JSON array value equivalent to `["Hello", "World"]`
+JsonValue arrayValue = JsonValue.from(List.of(
+ "Hello", "World"
+));
+
+// Create a JSON object value equivalent to `{ "a": 1, "b": 2 }`
+JsonValue objectValue = JsonValue.from(Map.of(
+ "a", 1,
+ "b", 2
+));
+
+// Create an arbitrarily nested JSON equivalent to:
+// {
+// "a": [1, 2],
+// "b": [3, 4]
+// }
+JsonValue complexValue = JsonValue.from(Map.of(
+ "a", List.of(
+ 1, 2
+ ),
+ "b", List.of(
+ 3, 4
+ )
+));
+```
+
+Normally a `Builder` class's `build` method will throw [`IllegalStateException`](https://docs.oracle.com/javase/8/docs/api/java/lang/IllegalStateException.html) if any required parameter or property is unset.
-To set a documented parameter or property to an undocumented or not yet supported _value_, pass a [`JsonValue`](arcade-java-core/src/main/kotlin/dev/arcade/core/JsonValue.kt) object to its setter:
+To forcibly omit a required parameter or property, pass [`JsonMissing`](arcade-java-core/src/main/kotlin/dev/arcade/core/Values.kt):
```java
-import dev.arcade.models.ExecuteToolRequest;
-import dev.arcade.models.ToolExecuteParams;
+import dev.arcade.core.JsonMissing;
+import dev.arcade.models.tools.ExecuteToolRequest;
+import dev.arcade.models.tools.ToolExecuteParams;
ToolExecuteParams params = ToolExecuteParams.builder()
.executeToolRequest(ExecuteToolRequest.builder()
.toolName("tool_name")
.build())
+ .toolName(JsonMissing.of())
.build();
```
@@ -377,7 +557,7 @@ By default, the SDK will not throw an exception in this case. It will throw [`Ar
If you would prefer to check that the response is completely well-typed upfront, then either call `validate()`:
```java
-import dev.arcade.models.ExecuteToolResponse;
+import dev.arcade.models.tools.ExecuteToolResponse;
ExecuteToolResponse executeToolResponse = client.tools().execute(params).validate();
```
@@ -385,9 +565,7 @@ ExecuteToolResponse executeToolResponse = client.tools().execute(params).validat
Or configure the method call to validate the response using the `responseValidation` method:
```java
-import dev.arcade.models.ExecuteToolRequest;
-import dev.arcade.models.ExecuteToolResponse;
-import dev.arcade.models.ToolExecuteParams;
+import dev.arcade.models.tools.ExecuteToolResponse;
ExecuteToolResponse executeToolResponse = client.tools().execute(
params, RequestOptions.builder().responseValidation(true).build()
diff --git a/SECURITY.md b/SECURITY.md
index 537fadc..c65353d 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -2,9 +2,9 @@
## Reporting Security Issues
-This SDK is generated by [Stainless Software Inc](http://stainlessapi.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken.
+This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken.
-To report a security issue, please contact the Stainless team at security@stainlessapi.com.
+To report a security issue, please contact the Stainless team at security@stainless.com.
## Responsible Disclosure
@@ -16,11 +16,11 @@ before making any information public.
## Reporting Non-SDK Related Security Issues
If you encounter security issues that are not directly related to SDKs but pertain to the services
-or products provided by Arcade please follow the respective company's security reporting guidelines.
+or products provided by Arcade, please follow the respective company's security reporting guidelines.
### Arcade Terms and Policies
-Please contact dev@arcade.dev for any questions or concerns regarding security of our services.
+Please contact dev@arcade.dev for any questions or concerns regarding the security of our services.
---
diff --git a/arcade-java-client-okhttp/build.gradle.kts b/arcade-java-client-okhttp/build.gradle.kts
index d6bc70f..3b56902 100644
--- a/arcade-java-client-okhttp/build.gradle.kts
+++ b/arcade-java-client-okhttp/build.gradle.kts
@@ -11,4 +11,5 @@ dependencies {
testImplementation(kotlin("test"))
testImplementation("org.assertj:assertj-core:3.25.3")
+ testImplementation("com.github.tomakehurst:wiremock-jre8:2.35.2")
}
diff --git a/arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/ArcadeOkHttpClient.kt b/arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/ArcadeOkHttpClient.kt
index cab17e4..5cc3e9d 100644
--- a/arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/ArcadeOkHttpClient.kt
+++ b/arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/ArcadeOkHttpClient.kt
@@ -6,18 +6,39 @@ import com.fasterxml.jackson.databind.json.JsonMapper
import dev.arcade.client.ArcadeClient
import dev.arcade.client.ArcadeClientImpl
import dev.arcade.core.ClientOptions
+import dev.arcade.core.Sleeper
+import dev.arcade.core.Timeout
+import dev.arcade.core.http.AsyncStreamResponse
import dev.arcade.core.http.Headers
+import dev.arcade.core.http.HttpClient
import dev.arcade.core.http.QueryParams
+import dev.arcade.core.jsonMapper
import java.net.Proxy
import java.time.Clock
import java.time.Duration
-
+import java.util.Optional
+import java.util.concurrent.Executor
+import javax.net.ssl.HostnameVerifier
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.X509TrustManager
+import kotlin.jvm.optionals.getOrNull
+
+/**
+ * A class that allows building an instance of [ArcadeClient] with [OkHttpClient] as the underlying
+ * [HttpClient].
+ */
class ArcadeOkHttpClient private constructor() {
companion object {
+ /** Returns a mutable builder for constructing an instance of [ArcadeClient]. */
@JvmStatic fun builder() = Builder()
+ /**
+ * Returns a client configured using system properties and environment variables.
+ *
+ * @see ClientOptions.Builder.fromEnv
+ */
@JvmStatic fun fromEnv(): ArcadeClient = builder().fromEnv().build()
}
@@ -25,20 +46,171 @@ class ArcadeOkHttpClient private constructor() {
class Builder internal constructor() {
private var clientOptions: ClientOptions.Builder = ClientOptions.builder()
- private var baseUrl: String = ClientOptions.PRODUCTION_URL
- // The default timeout for the client is 1 minute.
- private var timeout: Duration = Duration.ofSeconds(60)
private var proxy: Proxy? = null
+ private var sslSocketFactory: SSLSocketFactory? = null
+ private var trustManager: X509TrustManager? = null
+ private var hostnameVerifier: HostnameVerifier? = null
+
+ fun proxy(proxy: Proxy?) = apply { this.proxy = proxy }
+
+ /** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */
+ fun proxy(proxy: Optional) = proxy(proxy.getOrNull())
+
+ /**
+ * The socket factory used to secure HTTPS connections.
+ *
+ * If this is set, then [trustManager] must also be set.
+ *
+ * If unset, then the system default is used. Most applications should not call this method,
+ * and instead use the system default. The default include special optimizations that can be
+ * lost if the implementation is modified.
+ */
+ fun sslSocketFactory(sslSocketFactory: SSLSocketFactory?) = apply {
+ this.sslSocketFactory = sslSocketFactory
+ }
+
+ /** Alias for calling [Builder.sslSocketFactory] with `sslSocketFactory.orElse(null)`. */
+ fun sslSocketFactory(sslSocketFactory: Optional) =
+ sslSocketFactory(sslSocketFactory.getOrNull())
+
+ /**
+ * The trust manager used to secure HTTPS connections.
+ *
+ * If this is set, then [sslSocketFactory] must also be set.
+ *
+ * If unset, then the system default is used. Most applications should not call this method,
+ * and instead use the system default. The default include special optimizations that can be
+ * lost if the implementation is modified.
+ */
+ fun trustManager(trustManager: X509TrustManager?) = apply {
+ this.trustManager = trustManager
+ }
- fun baseUrl(baseUrl: String) = apply {
- clientOptions.baseUrl(baseUrl)
- this.baseUrl = baseUrl
+ /** Alias for calling [Builder.trustManager] with `trustManager.orElse(null)`. */
+ fun trustManager(trustManager: Optional) =
+ trustManager(trustManager.getOrNull())
+
+ /**
+ * The verifier used to confirm that response certificates apply to requested hostnames for
+ * HTTPS connections.
+ *
+ * If unset, then a default hostname verifier is used.
+ */
+ fun hostnameVerifier(hostnameVerifier: HostnameVerifier?) = apply {
+ this.hostnameVerifier = hostnameVerifier
}
+ /** Alias for calling [Builder.hostnameVerifier] with `hostnameVerifier.orElse(null)`. */
+ fun hostnameVerifier(hostnameVerifier: Optional) =
+ hostnameVerifier(hostnameVerifier.getOrNull())
+
+ /**
+ * Whether to throw an exception if any of the Jackson versions detected at runtime are
+ * incompatible with the SDK's minimum supported Jackson version (2.13.4).
+ *
+ * Defaults to true. Use extreme caution when disabling this option. There is no guarantee
+ * that the SDK will work correctly when using an incompatible Jackson version.
+ */
+ fun checkJacksonVersionCompatibility(checkJacksonVersionCompatibility: Boolean) = apply {
+ clientOptions.checkJacksonVersionCompatibility(checkJacksonVersionCompatibility)
+ }
+
+ /**
+ * The Jackson JSON mapper to use for serializing and deserializing JSON.
+ *
+ * Defaults to [dev.arcade.core.jsonMapper]. The default is usually sufficient and rarely
+ * needs to be overridden.
+ */
fun jsonMapper(jsonMapper: JsonMapper) = apply { clientOptions.jsonMapper(jsonMapper) }
+ /**
+ * The executor to use for running [AsyncStreamResponse.Handler] callbacks.
+ *
+ * Defaults to a dedicated cached thread pool.
+ *
+ * This class takes ownership of the executor and shuts it down, if possible, when closed.
+ */
+ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
+ clientOptions.streamHandlerExecutor(streamHandlerExecutor)
+ }
+
+ /**
+ * The interface to use for delaying execution, like during retries.
+ *
+ * This is primarily useful for using fake delays in tests.
+ *
+ * Defaults to real execution delays.
+ *
+ * This class takes ownership of the sleeper and closes it when closed.
+ */
+ fun sleeper(sleeper: Sleeper) = apply { clientOptions.sleeper(sleeper) }
+
+ /**
+ * The clock to use for operations that require timing, like retries.
+ *
+ * This is primarily useful for using a fake clock in tests.
+ *
+ * Defaults to [Clock.systemUTC].
+ */
fun clock(clock: Clock) = apply { clientOptions.clock(clock) }
+ /**
+ * The base URL to use for every request.
+ *
+ * Defaults to the production environment: `https://api.arcade.dev`.
+ */
+ fun baseUrl(baseUrl: String?) = apply { clientOptions.baseUrl(baseUrl) }
+
+ /** Alias for calling [Builder.baseUrl] with `baseUrl.orElse(null)`. */
+ fun baseUrl(baseUrl: Optional) = baseUrl(baseUrl.getOrNull())
+
+ /**
+ * Whether to call `validate` on every response before returning it.
+ *
+ * Defaults to false, which means the shape of the response will not be validated upfront.
+ * Instead, validation will only occur for the parts of the response that are accessed.
+ */
+ fun responseValidation(responseValidation: Boolean) = apply {
+ clientOptions.responseValidation(responseValidation)
+ }
+
+ /**
+ * Sets the maximum time allowed for various parts of an HTTP call's lifecycle, excluding
+ * retries.
+ *
+ * Defaults to [Timeout.default].
+ */
+ fun timeout(timeout: Timeout) = apply { clientOptions.timeout(timeout) }
+
+ /**
+ * Sets the maximum time allowed for a complete HTTP call, not including retries.
+ *
+ * See [Timeout.request] for more details.
+ *
+ * For fine-grained control, pass a [Timeout] object.
+ */
+ fun timeout(timeout: Duration) = apply { clientOptions.timeout(timeout) }
+
+ /**
+ * The maximum number of times to retry failed requests, with a short exponential backoff
+ * between requests.
+ *
+ * Only the following error types are retried:
+ * - Connection errors (for example, due to a network connectivity problem)
+ * - 408 Request Timeout
+ * - 409 Conflict
+ * - 429 Rate Limit
+ * - 5xx Internal
+ *
+ * The API may also explicitly instruct the SDK to retry or not retry a request.
+ *
+ * Defaults to 2.
+ */
+ fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
+
+ /** API key used for authorization in header */
+ fun apiKey(apiKey: String) = apply { clientOptions.apiKey(apiKey) }
+
fun headers(headers: Headers) = apply { clientOptions.headers(headers) }
fun headers(headers: Map>) = apply {
@@ -119,28 +291,28 @@ class ArcadeOkHttpClient private constructor() {
clientOptions.removeAllQueryParams(keys)
}
- fun timeout(timeout: Duration) = apply { this.timeout = timeout }
-
- fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
-
- fun proxy(proxy: Proxy) = apply { this.proxy = proxy }
-
- fun responseValidation(responseValidation: Boolean) = apply {
- clientOptions.responseValidation(responseValidation)
- }
-
- fun apiKey(apiKey: String) = apply { clientOptions.apiKey(apiKey) }
-
+ /**
+ * Updates configuration using system properties and environment variables.
+ *
+ * @see ClientOptions.Builder.fromEnv
+ */
fun fromEnv() = apply { clientOptions.fromEnv() }
+ /**
+ * Returns an immutable instance of [ArcadeClient].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ */
fun build(): ArcadeClient =
ArcadeClientImpl(
clientOptions
.httpClient(
OkHttpClient.builder()
- .baseUrl(baseUrl)
- .timeout(timeout)
+ .timeout(clientOptions.timeout())
.proxy(proxy)
+ .sslSocketFactory(sslSocketFactory)
+ .trustManager(trustManager)
+ .hostnameVerifier(hostnameVerifier)
.build()
)
.build()
diff --git a/arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/ArcadeOkHttpClientAsync.kt b/arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/ArcadeOkHttpClientAsync.kt
index 97d2a32..bfba8c6 100644
--- a/arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/ArcadeOkHttpClientAsync.kt
+++ b/arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/ArcadeOkHttpClientAsync.kt
@@ -6,18 +6,39 @@ import com.fasterxml.jackson.databind.json.JsonMapper
import dev.arcade.client.ArcadeClientAsync
import dev.arcade.client.ArcadeClientAsyncImpl
import dev.arcade.core.ClientOptions
+import dev.arcade.core.Sleeper
+import dev.arcade.core.Timeout
+import dev.arcade.core.http.AsyncStreamResponse
import dev.arcade.core.http.Headers
+import dev.arcade.core.http.HttpClient
import dev.arcade.core.http.QueryParams
+import dev.arcade.core.jsonMapper
import java.net.Proxy
import java.time.Clock
import java.time.Duration
-
+import java.util.Optional
+import java.util.concurrent.Executor
+import javax.net.ssl.HostnameVerifier
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.X509TrustManager
+import kotlin.jvm.optionals.getOrNull
+
+/**
+ * A class that allows building an instance of [ArcadeClientAsync] with [OkHttpClient] as the
+ * underlying [HttpClient].
+ */
class ArcadeOkHttpClientAsync private constructor() {
companion object {
+ /** Returns a mutable builder for constructing an instance of [ArcadeClientAsync]. */
@JvmStatic fun builder() = Builder()
+ /**
+ * Returns a client configured using system properties and environment variables.
+ *
+ * @see ClientOptions.Builder.fromEnv
+ */
@JvmStatic fun fromEnv(): ArcadeClientAsync = builder().fromEnv().build()
}
@@ -25,20 +46,171 @@ class ArcadeOkHttpClientAsync private constructor() {
class Builder internal constructor() {
private var clientOptions: ClientOptions.Builder = ClientOptions.builder()
- private var baseUrl: String = ClientOptions.PRODUCTION_URL
- // The default timeout for the client is 1 minute.
- private var timeout: Duration = Duration.ofSeconds(60)
private var proxy: Proxy? = null
+ private var sslSocketFactory: SSLSocketFactory? = null
+ private var trustManager: X509TrustManager? = null
+ private var hostnameVerifier: HostnameVerifier? = null
+
+ fun proxy(proxy: Proxy?) = apply { this.proxy = proxy }
+
+ /** Alias for calling [Builder.proxy] with `proxy.orElse(null)`. */
+ fun proxy(proxy: Optional) = proxy(proxy.getOrNull())
+
+ /**
+ * The socket factory used to secure HTTPS connections.
+ *
+ * If this is set, then [trustManager] must also be set.
+ *
+ * If unset, then the system default is used. Most applications should not call this method,
+ * and instead use the system default. The default include special optimizations that can be
+ * lost if the implementation is modified.
+ */
+ fun sslSocketFactory(sslSocketFactory: SSLSocketFactory?) = apply {
+ this.sslSocketFactory = sslSocketFactory
+ }
+
+ /** Alias for calling [Builder.sslSocketFactory] with `sslSocketFactory.orElse(null)`. */
+ fun sslSocketFactory(sslSocketFactory: Optional) =
+ sslSocketFactory(sslSocketFactory.getOrNull())
+
+ /**
+ * The trust manager used to secure HTTPS connections.
+ *
+ * If this is set, then [sslSocketFactory] must also be set.
+ *
+ * If unset, then the system default is used. Most applications should not call this method,
+ * and instead use the system default. The default include special optimizations that can be
+ * lost if the implementation is modified.
+ */
+ fun trustManager(trustManager: X509TrustManager?) = apply {
+ this.trustManager = trustManager
+ }
- fun baseUrl(baseUrl: String) = apply {
- clientOptions.baseUrl(baseUrl)
- this.baseUrl = baseUrl
+ /** Alias for calling [Builder.trustManager] with `trustManager.orElse(null)`. */
+ fun trustManager(trustManager: Optional) =
+ trustManager(trustManager.getOrNull())
+
+ /**
+ * The verifier used to confirm that response certificates apply to requested hostnames for
+ * HTTPS connections.
+ *
+ * If unset, then a default hostname verifier is used.
+ */
+ fun hostnameVerifier(hostnameVerifier: HostnameVerifier?) = apply {
+ this.hostnameVerifier = hostnameVerifier
}
+ /** Alias for calling [Builder.hostnameVerifier] with `hostnameVerifier.orElse(null)`. */
+ fun hostnameVerifier(hostnameVerifier: Optional) =
+ hostnameVerifier(hostnameVerifier.getOrNull())
+
+ /**
+ * Whether to throw an exception if any of the Jackson versions detected at runtime are
+ * incompatible with the SDK's minimum supported Jackson version (2.13.4).
+ *
+ * Defaults to true. Use extreme caution when disabling this option. There is no guarantee
+ * that the SDK will work correctly when using an incompatible Jackson version.
+ */
+ fun checkJacksonVersionCompatibility(checkJacksonVersionCompatibility: Boolean) = apply {
+ clientOptions.checkJacksonVersionCompatibility(checkJacksonVersionCompatibility)
+ }
+
+ /**
+ * The Jackson JSON mapper to use for serializing and deserializing JSON.
+ *
+ * Defaults to [dev.arcade.core.jsonMapper]. The default is usually sufficient and rarely
+ * needs to be overridden.
+ */
fun jsonMapper(jsonMapper: JsonMapper) = apply { clientOptions.jsonMapper(jsonMapper) }
+ /**
+ * The executor to use for running [AsyncStreamResponse.Handler] callbacks.
+ *
+ * Defaults to a dedicated cached thread pool.
+ *
+ * This class takes ownership of the executor and shuts it down, if possible, when closed.
+ */
+ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
+ clientOptions.streamHandlerExecutor(streamHandlerExecutor)
+ }
+
+ /**
+ * The interface to use for delaying execution, like during retries.
+ *
+ * This is primarily useful for using fake delays in tests.
+ *
+ * Defaults to real execution delays.
+ *
+ * This class takes ownership of the sleeper and closes it when closed.
+ */
+ fun sleeper(sleeper: Sleeper) = apply { clientOptions.sleeper(sleeper) }
+
+ /**
+ * The clock to use for operations that require timing, like retries.
+ *
+ * This is primarily useful for using a fake clock in tests.
+ *
+ * Defaults to [Clock.systemUTC].
+ */
fun clock(clock: Clock) = apply { clientOptions.clock(clock) }
+ /**
+ * The base URL to use for every request.
+ *
+ * Defaults to the production environment: `https://api.arcade.dev`.
+ */
+ fun baseUrl(baseUrl: String?) = apply { clientOptions.baseUrl(baseUrl) }
+
+ /** Alias for calling [Builder.baseUrl] with `baseUrl.orElse(null)`. */
+ fun baseUrl(baseUrl: Optional) = baseUrl(baseUrl.getOrNull())
+
+ /**
+ * Whether to call `validate` on every response before returning it.
+ *
+ * Defaults to false, which means the shape of the response will not be validated upfront.
+ * Instead, validation will only occur for the parts of the response that are accessed.
+ */
+ fun responseValidation(responseValidation: Boolean) = apply {
+ clientOptions.responseValidation(responseValidation)
+ }
+
+ /**
+ * Sets the maximum time allowed for various parts of an HTTP call's lifecycle, excluding
+ * retries.
+ *
+ * Defaults to [Timeout.default].
+ */
+ fun timeout(timeout: Timeout) = apply { clientOptions.timeout(timeout) }
+
+ /**
+ * Sets the maximum time allowed for a complete HTTP call, not including retries.
+ *
+ * See [Timeout.request] for more details.
+ *
+ * For fine-grained control, pass a [Timeout] object.
+ */
+ fun timeout(timeout: Duration) = apply { clientOptions.timeout(timeout) }
+
+ /**
+ * The maximum number of times to retry failed requests, with a short exponential backoff
+ * between requests.
+ *
+ * Only the following error types are retried:
+ * - Connection errors (for example, due to a network connectivity problem)
+ * - 408 Request Timeout
+ * - 409 Conflict
+ * - 429 Rate Limit
+ * - 5xx Internal
+ *
+ * The API may also explicitly instruct the SDK to retry or not retry a request.
+ *
+ * Defaults to 2.
+ */
+ fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
+
+ /** API key used for authorization in header */
+ fun apiKey(apiKey: String) = apply { clientOptions.apiKey(apiKey) }
+
fun headers(headers: Headers) = apply { clientOptions.headers(headers) }
fun headers(headers: Map>) = apply {
@@ -119,28 +291,28 @@ class ArcadeOkHttpClientAsync private constructor() {
clientOptions.removeAllQueryParams(keys)
}
- fun timeout(timeout: Duration) = apply { this.timeout = timeout }
-
- fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
-
- fun proxy(proxy: Proxy) = apply { this.proxy = proxy }
-
- fun responseValidation(responseValidation: Boolean) = apply {
- clientOptions.responseValidation(responseValidation)
- }
-
- fun apiKey(apiKey: String) = apply { clientOptions.apiKey(apiKey) }
-
+ /**
+ * Updates configuration using system properties and environment variables.
+ *
+ * @see ClientOptions.Builder.fromEnv
+ */
fun fromEnv() = apply { clientOptions.fromEnv() }
+ /**
+ * Returns an immutable instance of [ArcadeClientAsync].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ */
fun build(): ArcadeClientAsync =
ArcadeClientAsyncImpl(
clientOptions
.httpClient(
OkHttpClient.builder()
- .baseUrl(baseUrl)
- .timeout(timeout)
+ .timeout(clientOptions.timeout())
.proxy(proxy)
+ .sslSocketFactory(sslSocketFactory)
+ .trustManager(trustManager)
+ .hostnameVerifier(hostnameVerifier)
.build()
)
.build()
diff --git a/arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/OkHttpClient.kt b/arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/OkHttpClient.kt
index 55a1501..576da22 100644
--- a/arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/OkHttpClient.kt
+++ b/arcade-java-client-okhttp/src/main/kotlin/dev/arcade/client/okhttp/OkHttpClient.kt
@@ -1,7 +1,7 @@
package dev.arcade.client.okhttp
import dev.arcade.core.RequestOptions
-import dev.arcade.core.checkRequired
+import dev.arcade.core.Timeout
import dev.arcade.core.http.Headers
import dev.arcade.core.http.HttpClient
import dev.arcade.core.http.HttpMethod
@@ -13,10 +13,13 @@ import java.io.IOException
import java.io.InputStream
import java.net.Proxy
import java.time.Duration
+import java.util.concurrent.CancellationException
import java.util.concurrent.CompletableFuture
+import javax.net.ssl.HostnameVerifier
+import javax.net.ssl.SSLSocketFactory
+import javax.net.ssl.X509TrustManager
import okhttp3.Call
import okhttp3.Callback
-import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
@@ -28,8 +31,7 @@ import okhttp3.logging.HttpLoggingInterceptor
import okio.BufferedSink
class OkHttpClient
-private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val baseUrl: HttpUrl) :
- HttpClient {
+private constructor(@JvmSynthetic internal val okHttpClient: okhttp3.OkHttpClient) : HttpClient {
override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse {
val call = newCall(request, requestOptions)
@@ -49,20 +51,25 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
): CompletableFuture {
val future = CompletableFuture()
- request.body?.run { future.whenComplete { _, _ -> close() } }
-
- newCall(request, requestOptions)
- .enqueue(
- object : Callback {
- override fun onResponse(call: Call, response: Response) {
- future.complete(response.toResponse())
- }
+ val call = newCall(request, requestOptions)
+ call.enqueue(
+ object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ future.complete(response.toResponse())
+ }
- override fun onFailure(call: Call, e: IOException) {
- future.completeExceptionally(ArcadeIoException("Request failed", e))
- }
+ override fun onFailure(call: Call, e: IOException) {
+ future.completeExceptionally(ArcadeIoException("Request failed", e))
}
- )
+ }
+ )
+
+ future.whenComplete { _, e ->
+ if (e is CancellationException) {
+ call.cancel()
+ }
+ request.body?.close()
+ }
return future
}
@@ -88,13 +95,12 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
)
}
- val timeout = requestOptions.timeout
- if (timeout != null) {
+ requestOptions.timeout?.let {
clientBuilder
- .connectTimeout(timeout)
- .readTimeout(timeout)
- .writeTimeout(timeout)
- .callTimeout(if (timeout.seconds == 0L) timeout else timeout.plusSeconds(30))
+ .connectTimeout(it.connect())
+ .readTimeout(it.read())
+ .writeTimeout(it.write())
+ .callTimeout(it.request())
}
val client = clientBuilder.build()
@@ -109,19 +115,19 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
val builder = Request.Builder().url(toUrl()).method(method.name, body)
headers.names().forEach { name ->
- headers.values(name).forEach { builder.header(name, it) }
+ headers.values(name).forEach { builder.addHeader(name, it) }
}
if (
!headers.names().contains("X-Stainless-Read-Timeout") && client.readTimeoutMillis != 0
) {
- builder.header(
+ builder.addHeader(
"X-Stainless-Read-Timeout",
Duration.ofMillis(client.readTimeoutMillis.toLong()).seconds.toString(),
)
}
if (!headers.names().contains("X-Stainless-Timeout") && client.callTimeoutMillis != 0) {
- builder.header(
+ builder.addHeader(
"X-Stainless-Timeout",
Duration.ofMillis(client.callTimeoutMillis.toLong()).seconds.toString(),
)
@@ -140,11 +146,7 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
}
private fun HttpRequest.toUrl(): String {
- url?.let {
- return it
- }
-
- val builder = baseUrl.newBuilder()
+ val builder = baseUrl.toHttpUrl().newBuilder()
pathSegments.forEach(builder::addPathSegment)
queryParams.keys().forEach { key ->
queryParams.values(key).forEach { builder.addQueryParameter(key, it) }
@@ -194,27 +196,57 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
class Builder internal constructor() {
- private var baseUrl: HttpUrl? = null
- // The default timeout is 1 minute.
- private var timeout: Duration = Duration.ofSeconds(60)
+ private var timeout: Timeout = Timeout.default()
private var proxy: Proxy? = null
+ private var sslSocketFactory: SSLSocketFactory? = null
+ private var trustManager: X509TrustManager? = null
+ private var hostnameVerifier: HostnameVerifier? = null
- fun baseUrl(baseUrl: String) = apply { this.baseUrl = baseUrl.toHttpUrl() }
+ fun timeout(timeout: Timeout) = apply { this.timeout = timeout }
- fun timeout(timeout: Duration) = apply { this.timeout = timeout }
+ fun timeout(timeout: Duration) = timeout(Timeout.builder().request(timeout).build())
fun proxy(proxy: Proxy?) = apply { this.proxy = proxy }
+ fun sslSocketFactory(sslSocketFactory: SSLSocketFactory?) = apply {
+ this.sslSocketFactory = sslSocketFactory
+ }
+
+ fun trustManager(trustManager: X509TrustManager?) = apply {
+ this.trustManager = trustManager
+ }
+
+ fun hostnameVerifier(hostnameVerifier: HostnameVerifier?) = apply {
+ this.hostnameVerifier = hostnameVerifier
+ }
+
fun build(): OkHttpClient =
OkHttpClient(
okhttp3.OkHttpClient.Builder()
- .connectTimeout(timeout)
- .readTimeout(timeout)
- .writeTimeout(timeout)
- .callTimeout(if (timeout.seconds == 0L) timeout else timeout.plusSeconds(30))
+ .connectTimeout(timeout.connect())
+ .readTimeout(timeout.read())
+ .writeTimeout(timeout.write())
+ .callTimeout(timeout.request())
.proxy(proxy)
- .build(),
- checkRequired("baseUrl", baseUrl),
+ .apply {
+ val sslSocketFactory = sslSocketFactory
+ val trustManager = trustManager
+ if (sslSocketFactory != null && trustManager != null) {
+ sslSocketFactory(sslSocketFactory, trustManager)
+ } else {
+ check((sslSocketFactory != null) == (trustManager != null)) {
+ "Both or none of `sslSocketFactory` and `trustManager` must be set, but only one was set"
+ }
+ }
+
+ hostnameVerifier?.let(::hostnameVerifier)
+ }
+ .build()
+ .apply {
+ // We usually make all our requests to the same host so it makes sense to
+ // raise the per-host limit to the overall limit.
+ dispatcher.maxRequestsPerHost = dispatcher.maxRequests
+ }
)
}
}
diff --git a/arcade-java-client-okhttp/src/test/kotlin/dev/arcade/client/okhttp/OkHttpClientTest.kt b/arcade-java-client-okhttp/src/test/kotlin/dev/arcade/client/okhttp/OkHttpClientTest.kt
new file mode 100644
index 0000000..fcd93ab
--- /dev/null
+++ b/arcade-java-client-okhttp/src/test/kotlin/dev/arcade/client/okhttp/OkHttpClientTest.kt
@@ -0,0 +1,44 @@
+package dev.arcade.client.okhttp
+
+import com.github.tomakehurst.wiremock.client.WireMock.*
+import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo
+import com.github.tomakehurst.wiremock.junit5.WireMockTest
+import dev.arcade.core.http.HttpMethod
+import dev.arcade.core.http.HttpRequest
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.parallel.ResourceLock
+
+@WireMockTest
+@ResourceLock("https://github.com/wiremock/wiremock/issues/169")
+internal class OkHttpClientTest {
+
+ private lateinit var baseUrl: String
+ private lateinit var httpClient: OkHttpClient
+
+ @BeforeEach
+ fun beforeEach(wmRuntimeInfo: WireMockRuntimeInfo) {
+ baseUrl = wmRuntimeInfo.httpBaseUrl
+ httpClient = OkHttpClient.builder().build()
+ }
+
+ @Test
+ fun executeAsync_whenFutureCancelled_cancelsUnderlyingCall() {
+ stubFor(post(urlPathEqualTo("/something")).willReturn(ok()))
+ val responseFuture =
+ httpClient.executeAsync(
+ HttpRequest.builder()
+ .method(HttpMethod.POST)
+ .baseUrl(baseUrl)
+ .addPathSegment("something")
+ .build()
+ )
+ val call = httpClient.okHttpClient.dispatcher.runningCalls().single()
+
+ responseFuture.cancel(false)
+
+ // Should have cancelled the underlying call
+ assertThat(call.isCanceled()).isTrue()
+ }
+}
diff --git a/arcade-java-core/build.gradle.kts b/arcade-java-core/build.gradle.kts
index fe1757b..f6aed05 100644
--- a/arcade-java-core/build.gradle.kts
+++ b/arcade-java-core/build.gradle.kts
@@ -3,14 +3,28 @@ plugins {
id("arcade.publish")
}
+configurations.all {
+ resolutionStrategy {
+ // Compile and test against a lower Jackson version to ensure we're compatible with it.
+ // We publish with a higher version (see below) to ensure users depend on a secure version by default.
+ force("com.fasterxml.jackson.core:jackson-core:2.13.4")
+ force("com.fasterxml.jackson.core:jackson-databind:2.13.4")
+ force("com.fasterxml.jackson.core:jackson-annotations:2.13.4")
+ force("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.4")
+ force("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.4")
+ force("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.4")
+ }
+}
+
dependencies {
- api("com.fasterxml.jackson.core:jackson-core:2.18.1")
- api("com.fasterxml.jackson.core:jackson-databind:2.18.1")
+ api("com.fasterxml.jackson.core:jackson-core:2.18.2")
+ api("com.fasterxml.jackson.core:jackson-databind:2.18.2")
+ api("com.google.errorprone:error_prone_annotations:2.33.0")
- implementation("com.fasterxml.jackson.core:jackson-annotations:2.18.1")
- implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.1")
- implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.1")
- implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.18.1")
+ implementation("com.fasterxml.jackson.core:jackson-annotations:2.18.2")
+ implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.2")
+ implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2")
+ implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.18.2")
implementation("org.apache.httpcomponents.core5:httpcore5:5.2.4")
implementation("org.apache.httpcomponents.client5:httpclient5:5.3.1")
@@ -20,6 +34,7 @@ dependencies {
testImplementation("org.assertj:assertj-core:3.25.3")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.3")
+ testImplementation("org.junit-pioneer:junit-pioneer:1.9.1")
testImplementation("org.mockito:mockito-core:5.14.2")
testImplementation("org.mockito:mockito-junit-jupiter:5.14.2")
testImplementation("org.mockito.kotlin:mockito-kotlin:4.1.0")
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClient.kt b/arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClient.kt
index bf05325..167ce86 100644
--- a/arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClient.kt
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClient.kt
@@ -2,12 +2,14 @@
package dev.arcade.client
+import dev.arcade.core.ClientOptions
import dev.arcade.services.blocking.AdminService
import dev.arcade.services.blocking.AuthService
import dev.arcade.services.blocking.ChatService
import dev.arcade.services.blocking.HealthService
import dev.arcade.services.blocking.ToolService
import dev.arcade.services.blocking.WorkerService
+import java.util.function.Consumer
/**
* A client for interacting with the Arcade REST API synchronously. You can also switch to
@@ -33,6 +35,18 @@ interface ArcadeClient {
*/
fun async(): ArcadeClientAsync
+ /**
+ * Returns a view of this service that provides access to raw HTTP responses for each method.
+ */
+ fun withRawResponse(): WithRawResponse
+
+ /**
+ * Returns a view of this service with the given option modifications applied.
+ *
+ * The original service is not modified.
+ */
+ fun withOptions(modifier: Consumer): ArcadeClient
+
fun admin(): AdminService
fun auth(): AuthService
@@ -57,4 +71,27 @@ interface ArcadeClient {
* method.
*/
fun close()
+
+ /** A view of [ArcadeClient] that provides access to raw HTTP responses for each method. */
+ interface WithRawResponse {
+
+ /**
+ * Returns a view of this service with the given option modifications applied.
+ *
+ * The original service is not modified.
+ */
+ fun withOptions(modifier: Consumer): ArcadeClient.WithRawResponse
+
+ fun admin(): AdminService.WithRawResponse
+
+ fun auth(): AuthService.WithRawResponse
+
+ fun health(): HealthService.WithRawResponse
+
+ fun chat(): ChatService.WithRawResponse
+
+ fun tools(): ToolService.WithRawResponse
+
+ fun workers(): WorkerService.WithRawResponse
+ }
}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientAsync.kt b/arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientAsync.kt
index 9830ae4..fddb75a 100644
--- a/arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientAsync.kt
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientAsync.kt
@@ -2,12 +2,14 @@
package dev.arcade.client
+import dev.arcade.core.ClientOptions
import dev.arcade.services.async.AdminServiceAsync
import dev.arcade.services.async.AuthServiceAsync
import dev.arcade.services.async.ChatServiceAsync
import dev.arcade.services.async.HealthServiceAsync
import dev.arcade.services.async.ToolServiceAsync
import dev.arcade.services.async.WorkerServiceAsync
+import java.util.function.Consumer
/**
* A client for interacting with the Arcade REST API asynchronously. You can also switch to
@@ -33,6 +35,18 @@ interface ArcadeClientAsync {
*/
fun sync(): ArcadeClient
+ /**
+ * Returns a view of this service that provides access to raw HTTP responses for each method.
+ */
+ fun withRawResponse(): WithRawResponse
+
+ /**
+ * Returns a view of this service with the given option modifications applied.
+ *
+ * The original service is not modified.
+ */
+ fun withOptions(modifier: Consumer): ArcadeClientAsync
+
fun admin(): AdminServiceAsync
fun auth(): AuthServiceAsync
@@ -57,4 +71,29 @@ interface ArcadeClientAsync {
* method.
*/
fun close()
+
+ /** A view of [ArcadeClientAsync] that provides access to raw HTTP responses for each method. */
+ interface WithRawResponse {
+
+ /**
+ * Returns a view of this service with the given option modifications applied.
+ *
+ * The original service is not modified.
+ */
+ fun withOptions(
+ modifier: Consumer
+ ): ArcadeClientAsync.WithRawResponse
+
+ fun admin(): AdminServiceAsync.WithRawResponse
+
+ fun auth(): AuthServiceAsync.WithRawResponse
+
+ fun health(): HealthServiceAsync.WithRawResponse
+
+ fun chat(): ChatServiceAsync.WithRawResponse
+
+ fun tools(): ToolServiceAsync.WithRawResponse
+
+ fun workers(): WorkerServiceAsync.WithRawResponse
+ }
}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientAsyncImpl.kt b/arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientAsyncImpl.kt
index 7b822b1..b644fcb 100644
--- a/arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientAsyncImpl.kt
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientAsyncImpl.kt
@@ -16,6 +16,7 @@ import dev.arcade.services.async.ToolServiceAsync
import dev.arcade.services.async.ToolServiceAsyncImpl
import dev.arcade.services.async.WorkerServiceAsync
import dev.arcade.services.async.WorkerServiceAsyncImpl
+import java.util.function.Consumer
class ArcadeClientAsyncImpl(private val clientOptions: ClientOptions) : ArcadeClientAsync {
@@ -30,6 +31,10 @@ class ArcadeClientAsyncImpl(private val clientOptions: ClientOptions) : ArcadeCl
// Pass the original clientOptions so that this client sets its own User-Agent.
private val sync: ArcadeClient by lazy { ArcadeClientImpl(clientOptions) }
+ private val withRawResponse: ArcadeClientAsync.WithRawResponse by lazy {
+ WithRawResponseImpl(clientOptions)
+ }
+
private val admin: AdminServiceAsync by lazy {
AdminServiceAsyncImpl(clientOptionsWithUserAgent)
}
@@ -50,6 +55,11 @@ class ArcadeClientAsyncImpl(private val clientOptions: ClientOptions) : ArcadeCl
override fun sync(): ArcadeClient = sync
+ override fun withRawResponse(): ArcadeClientAsync.WithRawResponse = withRawResponse
+
+ override fun withOptions(modifier: Consumer): ArcadeClientAsync =
+ ArcadeClientAsyncImpl(clientOptions.toBuilder().apply(modifier::accept).build())
+
override fun admin(): AdminServiceAsync = admin
override fun auth(): AuthServiceAsync = auth
@@ -62,5 +72,52 @@ class ArcadeClientAsyncImpl(private val clientOptions: ClientOptions) : ArcadeCl
override fun workers(): WorkerServiceAsync = workers
- override fun close() = clientOptions.httpClient.close()
+ override fun close() = clientOptions.close()
+
+ class WithRawResponseImpl internal constructor(private val clientOptions: ClientOptions) :
+ ArcadeClientAsync.WithRawResponse {
+
+ private val admin: AdminServiceAsync.WithRawResponse by lazy {
+ AdminServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val auth: AuthServiceAsync.WithRawResponse by lazy {
+ AuthServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val health: HealthServiceAsync.WithRawResponse by lazy {
+ HealthServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val chat: ChatServiceAsync.WithRawResponse by lazy {
+ ChatServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val tools: ToolServiceAsync.WithRawResponse by lazy {
+ ToolServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val workers: WorkerServiceAsync.WithRawResponse by lazy {
+ WorkerServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ override fun withOptions(
+ modifier: Consumer
+ ): ArcadeClientAsync.WithRawResponse =
+ ArcadeClientAsyncImpl.WithRawResponseImpl(
+ clientOptions.toBuilder().apply(modifier::accept).build()
+ )
+
+ override fun admin(): AdminServiceAsync.WithRawResponse = admin
+
+ override fun auth(): AuthServiceAsync.WithRawResponse = auth
+
+ override fun health(): HealthServiceAsync.WithRawResponse = health
+
+ override fun chat(): ChatServiceAsync.WithRawResponse = chat
+
+ override fun tools(): ToolServiceAsync.WithRawResponse = tools
+
+ override fun workers(): WorkerServiceAsync.WithRawResponse = workers
+ }
}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientImpl.kt b/arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientImpl.kt
index d8a7714..c399738 100644
--- a/arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientImpl.kt
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/client/ArcadeClientImpl.kt
@@ -16,6 +16,7 @@ import dev.arcade.services.blocking.ToolService
import dev.arcade.services.blocking.ToolServiceImpl
import dev.arcade.services.blocking.WorkerService
import dev.arcade.services.blocking.WorkerServiceImpl
+import java.util.function.Consumer
class ArcadeClientImpl(private val clientOptions: ClientOptions) : ArcadeClient {
@@ -30,6 +31,10 @@ class ArcadeClientImpl(private val clientOptions: ClientOptions) : ArcadeClient
// Pass the original clientOptions so that this client sets its own User-Agent.
private val async: ArcadeClientAsync by lazy { ArcadeClientAsyncImpl(clientOptions) }
+ private val withRawResponse: ArcadeClient.WithRawResponse by lazy {
+ WithRawResponseImpl(clientOptions)
+ }
+
private val admin: AdminService by lazy { AdminServiceImpl(clientOptionsWithUserAgent) }
private val auth: AuthService by lazy { AuthServiceImpl(clientOptionsWithUserAgent) }
@@ -44,6 +49,11 @@ class ArcadeClientImpl(private val clientOptions: ClientOptions) : ArcadeClient
override fun async(): ArcadeClientAsync = async
+ override fun withRawResponse(): ArcadeClient.WithRawResponse = withRawResponse
+
+ override fun withOptions(modifier: Consumer): ArcadeClient =
+ ArcadeClientImpl(clientOptions.toBuilder().apply(modifier::accept).build())
+
override fun admin(): AdminService = admin
override fun auth(): AuthService = auth
@@ -56,5 +66,52 @@ class ArcadeClientImpl(private val clientOptions: ClientOptions) : ArcadeClient
override fun workers(): WorkerService = workers
- override fun close() = clientOptions.httpClient.close()
+ override fun close() = clientOptions.close()
+
+ class WithRawResponseImpl internal constructor(private val clientOptions: ClientOptions) :
+ ArcadeClient.WithRawResponse {
+
+ private val admin: AdminService.WithRawResponse by lazy {
+ AdminServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val auth: AuthService.WithRawResponse by lazy {
+ AuthServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val health: HealthService.WithRawResponse by lazy {
+ HealthServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val chat: ChatService.WithRawResponse by lazy {
+ ChatServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val tools: ToolService.WithRawResponse by lazy {
+ ToolServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val workers: WorkerService.WithRawResponse by lazy {
+ WorkerServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ override fun withOptions(
+ modifier: Consumer
+ ): ArcadeClient.WithRawResponse =
+ ArcadeClientImpl.WithRawResponseImpl(
+ clientOptions.toBuilder().apply(modifier::accept).build()
+ )
+
+ override fun admin(): AdminService.WithRawResponse = admin
+
+ override fun auth(): AuthService.WithRawResponse = auth
+
+ override fun health(): HealthService.WithRawResponse = health
+
+ override fun chat(): ChatService.WithRawResponse = chat
+
+ override fun tools(): ToolService.WithRawResponse = tools
+
+ override fun workers(): WorkerService.WithRawResponse = workers
+ }
}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/AutoPager.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/AutoPager.kt
new file mode 100644
index 0000000..1b8c446
--- /dev/null
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/AutoPager.kt
@@ -0,0 +1,21 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package dev.arcade.core
+
+import java.util.stream.Stream
+import java.util.stream.StreamSupport
+
+class AutoPager private constructor(private val firstPage: Page) : Iterable {
+
+ companion object {
+
+ fun from(firstPage: Page): AutoPager = AutoPager(firstPage)
+ }
+
+ override fun iterator(): Iterator =
+ generateSequence(firstPage) { if (it.hasNextPage()) it.nextPage() else null }
+ .flatMap { it.items() }
+ .iterator()
+
+ fun stream(): Stream = StreamSupport.stream(spliterator(), false)
+}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/AutoPagerAsync.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/AutoPagerAsync.kt
new file mode 100644
index 0000000..5732bb0
--- /dev/null
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/AutoPagerAsync.kt
@@ -0,0 +1,88 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package dev.arcade.core
+
+import dev.arcade.core.http.AsyncStreamResponse
+import java.util.Optional
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.CompletionException
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicReference
+
+class AutoPagerAsync
+private constructor(private val firstPage: PageAsync, private val defaultExecutor: Executor) :
+ AsyncStreamResponse {
+
+ companion object {
+
+ fun from(firstPage: PageAsync, defaultExecutor: Executor): AutoPagerAsync =
+ AutoPagerAsync(firstPage, defaultExecutor)
+ }
+
+ private val onCompleteFuture = CompletableFuture()
+ private val state = AtomicReference(State.NEW)
+
+ override fun subscribe(handler: AsyncStreamResponse.Handler): AsyncStreamResponse =
+ subscribe(handler, defaultExecutor)
+
+ override fun subscribe(
+ handler: AsyncStreamResponse.Handler,
+ executor: Executor,
+ ): AsyncStreamResponse = apply {
+ // TODO(JDK): Use `compareAndExchange` once targeting JDK 9.
+ check(state.compareAndSet(State.NEW, State.SUBSCRIBED)) {
+ if (state.get() == State.SUBSCRIBED) "Cannot subscribe more than once"
+ else "Cannot subscribe after the response is closed"
+ }
+
+ fun PageAsync.handle(): CompletableFuture {
+ if (state.get() == State.CLOSED) {
+ return CompletableFuture.completedFuture(null)
+ }
+
+ items().forEach { handler.onNext(it) }
+ return if (hasNextPage()) nextPage().thenCompose { it.handle() }
+ else CompletableFuture.completedFuture(null)
+ }
+
+ executor.execute {
+ firstPage.handle().whenComplete { _, error ->
+ val actualError =
+ if (error is CompletionException && error.cause != null) error.cause else error
+ try {
+ handler.onComplete(Optional.ofNullable(actualError))
+ } finally {
+ try {
+ if (actualError == null) {
+ onCompleteFuture.complete(null)
+ } else {
+ onCompleteFuture.completeExceptionally(actualError)
+ }
+ } finally {
+ close()
+ }
+ }
+ }
+ }
+ }
+
+ override fun onCompleteFuture(): CompletableFuture = onCompleteFuture
+
+ override fun close() {
+ val previousState = state.getAndSet(State.CLOSED)
+ if (previousState == State.CLOSED) {
+ return
+ }
+
+ // When the stream is closed, we should always consider it closed. If it closed due
+ // to an error, then we will have already completed the future earlier, and this
+ // will be a no-op.
+ onCompleteFuture.complete(null)
+ }
+}
+
+private enum class State {
+ NEW,
+ SUBSCRIBED,
+ CLOSED,
+}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/BaseDeserializer.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/BaseDeserializer.kt
index 598a9c6..cfff911 100644
--- a/arcade-java-core/src/main/kotlin/dev/arcade/core/BaseDeserializer.kt
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/BaseDeserializer.kt
@@ -7,7 +7,6 @@ import com.fasterxml.jackson.databind.BeanProperty
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.JsonDeserializer
-import com.fasterxml.jackson.databind.JsonMappingException
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.deser.ContextualDeserializer
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
@@ -29,31 +28,17 @@ abstract class BaseDeserializer(type: KClass) :
protected abstract fun ObjectCodec.deserialize(node: JsonNode): T
- protected fun ObjectCodec.tryDeserialize(
- node: JsonNode,
- type: TypeReference,
- validate: (T) -> Unit = {},
- ): T? {
- return try {
- readValue(treeAsTokens(node), type).apply(validate)
- } catch (e: JsonMappingException) {
- null
- } catch (e: RuntimeException) {
+ protected fun ObjectCodec.tryDeserialize(node: JsonNode, type: TypeReference): T? =
+ try {
+ readValue(treeAsTokens(node), type)
+ } catch (e: Exception) {
null
}
- }
- protected fun ObjectCodec.tryDeserialize(
- node: JsonNode,
- type: JavaType,
- validate: (T) -> Unit = {},
- ): T? {
- return try {
- readValue(treeAsTokens(node), type).apply(validate)
- } catch (e: JsonMappingException) {
- null
- } catch (e: RuntimeException) {
+ protected fun ObjectCodec.tryDeserialize(node: JsonNode, type: JavaType): T? =
+ try {
+ readValue(treeAsTokens(node), type)
+ } catch (e: Exception) {
null
}
- }
}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/Check.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/Check.kt
index df8badc..45eecc4 100644
--- a/arcade-java-core/src/main/kotlin/dev/arcade/core/Check.kt
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/Check.kt
@@ -2,9 +2,27 @@
package dev.arcade.core
+import com.fasterxml.jackson.core.Version
+import com.fasterxml.jackson.core.util.VersionUtil
+
+fun checkRequired(name: String, condition: Boolean) =
+ check(condition) { "`$name` is required, but was not set" }
+
fun checkRequired(name: String, value: T?): T =
checkNotNull(value) { "`$name` is required, but was not set" }
+@JvmSynthetic
+internal fun checkKnown(name: String, value: JsonField): T =
+ value.asKnown().orElseThrow {
+ IllegalStateException("`$name` is not a known type: ${value.javaClass.simpleName}")
+ }
+
+@JvmSynthetic
+internal fun checkKnown(name: String, value: MultipartField): T =
+ value.value.asKnown().orElseThrow {
+ IllegalStateException("`$name` is not a known type: ${value.javaClass.simpleName}")
+ }
+
@JvmSynthetic
internal fun checkLength(name: String, value: String, length: Int): String =
value.also {
@@ -27,3 +45,52 @@ internal fun checkMaxLength(name: String, value: String, maxLength: Int): String
"`$name` must have at most length $maxLength, but was ${it.length}"
}
}
+
+@JvmSynthetic
+internal fun checkJacksonVersionCompatibility() {
+ val incompatibleJacksonVersions =
+ RUNTIME_JACKSON_VERSIONS.mapNotNull {
+ val badVersionReason = BAD_JACKSON_VERSIONS[it.toString()]
+ when {
+ it.majorVersion != MINIMUM_JACKSON_VERSION.majorVersion ->
+ it to "incompatible major version"
+ it.minorVersion < MINIMUM_JACKSON_VERSION.minorVersion ->
+ it to "minor version too low"
+ it.minorVersion == MINIMUM_JACKSON_VERSION.minorVersion &&
+ it.patchLevel < MINIMUM_JACKSON_VERSION.patchLevel ->
+ it to "patch version too low"
+ badVersionReason != null -> it to badVersionReason
+ else -> null
+ }
+ }
+ check(incompatibleJacksonVersions.isEmpty()) {
+ """
+This SDK requires a minimum Jackson version of $MINIMUM_JACKSON_VERSION, but the following incompatible Jackson versions were detected at runtime:
+
+${incompatibleJacksonVersions.asSequence().map { (version, incompatibilityReason) ->
+ "- `${version.toFullString().replace("/", ":")}` ($incompatibilityReason)"
+}.joinToString("\n")}
+
+This can happen if you are either:
+1. Directly depending on different Jackson versions
+2. Depending on some library that depends on different Jackson versions, potentially transitively
+
+Double-check that you are depending on compatible Jackson versions.
+
+See https://www.github.com/ArcadeAI/arcade-java#jackson for more information.
+ """
+ .trimIndent()
+ }
+}
+
+private val MINIMUM_JACKSON_VERSION: Version = VersionUtil.parseVersion("2.13.4", null, null)
+private val BAD_JACKSON_VERSIONS: Map =
+ mapOf("2.18.1" to "due to https://github.com/FasterXML/jackson-databind/issues/4639")
+private val RUNTIME_JACKSON_VERSIONS: List =
+ listOf(
+ com.fasterxml.jackson.core.json.PackageVersion.VERSION,
+ com.fasterxml.jackson.databind.cfg.PackageVersion.VERSION,
+ com.fasterxml.jackson.datatype.jdk8.PackageVersion.VERSION,
+ com.fasterxml.jackson.datatype.jsr310.PackageVersion.VERSION,
+ com.fasterxml.jackson.module.kotlin.PackageVersion.VERSION,
+ )
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/ClientOptions.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/ClientOptions.kt
index a794feb..f262cd6 100644
--- a/arcade-java-core/src/main/kotlin/dev/arcade/core/ClientOptions.kt
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/ClientOptions.kt
@@ -3,35 +3,149 @@
package dev.arcade.core
import com.fasterxml.jackson.databind.json.JsonMapper
+import dev.arcade.core.http.AsyncStreamResponse
import dev.arcade.core.http.Headers
import dev.arcade.core.http.HttpClient
import dev.arcade.core.http.PhantomReachableClosingHttpClient
import dev.arcade.core.http.QueryParams
import dev.arcade.core.http.RetryingHttpClient
import java.time.Clock
-
+import java.time.Duration
+import java.util.Optional
+import java.util.concurrent.Executor
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import java.util.concurrent.ThreadFactory
+import java.util.concurrent.atomic.AtomicLong
+import kotlin.jvm.optionals.getOrNull
+
+/** A class representing the SDK client configuration. */
class ClientOptions
private constructor(
private val originalHttpClient: HttpClient,
+ /**
+ * The HTTP client to use in the SDK.
+ *
+ * Use the one published in `arcade-java-client-okhttp` or implement your own.
+ *
+ * This class takes ownership of the client and closes it when closed.
+ */
@get:JvmName("httpClient") val httpClient: HttpClient,
+ /**
+ * Whether to throw an exception if any of the Jackson versions detected at runtime are
+ * incompatible with the SDK's minimum supported Jackson version (2.13.4).
+ *
+ * Defaults to true. Use extreme caution when disabling this option. There is no guarantee that
+ * the SDK will work correctly when using an incompatible Jackson version.
+ */
+ @get:JvmName("checkJacksonVersionCompatibility") val checkJacksonVersionCompatibility: Boolean,
+ /**
+ * The Jackson JSON mapper to use for serializing and deserializing JSON.
+ *
+ * Defaults to [dev.arcade.core.jsonMapper]. The default is usually sufficient and rarely needs
+ * to be overridden.
+ */
@get:JvmName("jsonMapper") val jsonMapper: JsonMapper,
+ /**
+ * The executor to use for running [AsyncStreamResponse.Handler] callbacks.
+ *
+ * Defaults to a dedicated cached thread pool.
+ *
+ * This class takes ownership of the executor and shuts it down, if possible, when closed.
+ */
+ @get:JvmName("streamHandlerExecutor") val streamHandlerExecutor: Executor,
+ /**
+ * The interface to use for delaying execution, like during retries.
+ *
+ * This is primarily useful for using fake delays in tests.
+ *
+ * Defaults to real execution delays.
+ *
+ * This class takes ownership of the sleeper and closes it when closed.
+ */
+ @get:JvmName("sleeper") val sleeper: Sleeper,
+ /**
+ * The clock to use for operations that require timing, like retries.
+ *
+ * This is primarily useful for using a fake clock in tests.
+ *
+ * Defaults to [Clock.systemUTC].
+ */
@get:JvmName("clock") val clock: Clock,
- @get:JvmName("baseUrl") val baseUrl: String,
+ private val baseUrl: String?,
+ /** Headers to send with the request. */
@get:JvmName("headers") val headers: Headers,
+ /** Query params to send with the request. */
@get:JvmName("queryParams") val queryParams: QueryParams,
+ /**
+ * Whether to call `validate` on every response before returning it.
+ *
+ * Defaults to false, which means the shape of the response will not be validated upfront.
+ * Instead, validation will only occur for the parts of the response that are accessed.
+ */
@get:JvmName("responseValidation") val responseValidation: Boolean,
+ /**
+ * Sets the maximum time allowed for various parts of an HTTP call's lifecycle, excluding
+ * retries.
+ *
+ * Defaults to [Timeout.default].
+ */
+ @get:JvmName("timeout") val timeout: Timeout,
+ /**
+ * The maximum number of times to retry failed requests, with a short exponential backoff
+ * between requests.
+ *
+ * Only the following error types are retried:
+ * - Connection errors (for example, due to a network connectivity problem)
+ * - 408 Request Timeout
+ * - 409 Conflict
+ * - 429 Rate Limit
+ * - 5xx Internal
+ *
+ * The API may also explicitly instruct the SDK to retry or not retry a request.
+ *
+ * Defaults to 2.
+ */
@get:JvmName("maxRetries") val maxRetries: Int,
+ /** API key used for authorization in header */
@get:JvmName("apiKey") val apiKey: String,
) {
+ init {
+ if (checkJacksonVersionCompatibility) {
+ checkJacksonVersionCompatibility()
+ }
+ }
+
+ /**
+ * The base URL to use for every request.
+ *
+ * Defaults to the production environment: `https://api.arcade.dev`.
+ */
+ fun baseUrl(): String = baseUrl ?: PRODUCTION_URL
+
fun toBuilder() = Builder().from(this)
companion object {
const val PRODUCTION_URL = "https://api.arcade.dev"
+ /**
+ * Returns a mutable builder for constructing an instance of [ClientOptions].
+ *
+ * The following fields are required:
+ * ```java
+ * .httpClient()
+ * .apiKey()
+ * ```
+ */
@JvmStatic fun builder() = Builder()
+ /**
+ * Returns options configured using system properties and environment variables.
+ *
+ * @see Builder.fromEnv
+ */
@JvmStatic fun fromEnv(): ClientOptions = builder().fromEnv().build()
}
@@ -39,42 +153,155 @@ private constructor(
class Builder internal constructor() {
private var httpClient: HttpClient? = null
+ private var checkJacksonVersionCompatibility: Boolean = true
private var jsonMapper: JsonMapper = jsonMapper()
+ private var streamHandlerExecutor: Executor? = null
+ private var sleeper: Sleeper? = null
private var clock: Clock = Clock.systemUTC()
- private var baseUrl: String = PRODUCTION_URL
+ private var baseUrl: String? = null
private var headers: Headers.Builder = Headers.builder()
private var queryParams: QueryParams.Builder = QueryParams.builder()
private var responseValidation: Boolean = false
+ private var timeout: Timeout = Timeout.default()
private var maxRetries: Int = 2
private var apiKey: String? = null
@JvmSynthetic
internal fun from(clientOptions: ClientOptions) = apply {
httpClient = clientOptions.originalHttpClient
+ checkJacksonVersionCompatibility = clientOptions.checkJacksonVersionCompatibility
jsonMapper = clientOptions.jsonMapper
+ streamHandlerExecutor = clientOptions.streamHandlerExecutor
+ sleeper = clientOptions.sleeper
clock = clientOptions.clock
baseUrl = clientOptions.baseUrl
headers = clientOptions.headers.toBuilder()
queryParams = clientOptions.queryParams.toBuilder()
responseValidation = clientOptions.responseValidation
+ timeout = clientOptions.timeout
maxRetries = clientOptions.maxRetries
apiKey = clientOptions.apiKey
}
- fun httpClient(httpClient: HttpClient) = apply { this.httpClient = httpClient }
+ /**
+ * The HTTP client to use in the SDK.
+ *
+ * Use the one published in `arcade-java-client-okhttp` or implement your own.
+ *
+ * This class takes ownership of the client and closes it when closed.
+ */
+ fun httpClient(httpClient: HttpClient) = apply {
+ this.httpClient = PhantomReachableClosingHttpClient(httpClient)
+ }
+
+ /**
+ * Whether to throw an exception if any of the Jackson versions detected at runtime are
+ * incompatible with the SDK's minimum supported Jackson version (2.13.4).
+ *
+ * Defaults to true. Use extreme caution when disabling this option. There is no guarantee
+ * that the SDK will work correctly when using an incompatible Jackson version.
+ */
+ fun checkJacksonVersionCompatibility(checkJacksonVersionCompatibility: Boolean) = apply {
+ this.checkJacksonVersionCompatibility = checkJacksonVersionCompatibility
+ }
+ /**
+ * The Jackson JSON mapper to use for serializing and deserializing JSON.
+ *
+ * Defaults to [dev.arcade.core.jsonMapper]. The default is usually sufficient and rarely
+ * needs to be overridden.
+ */
fun jsonMapper(jsonMapper: JsonMapper) = apply { this.jsonMapper = jsonMapper }
- fun clock(clock: Clock) = apply { this.clock = clock }
+ /**
+ * The executor to use for running [AsyncStreamResponse.Handler] callbacks.
+ *
+ * Defaults to a dedicated cached thread pool.
+ *
+ * This class takes ownership of the executor and shuts it down, if possible, when closed.
+ */
+ fun streamHandlerExecutor(streamHandlerExecutor: Executor) = apply {
+ this.streamHandlerExecutor =
+ if (streamHandlerExecutor is ExecutorService)
+ PhantomReachableExecutorService(streamHandlerExecutor)
+ else streamHandlerExecutor
+ }
- fun baseUrl(baseUrl: String) = apply { this.baseUrl = baseUrl }
+ /**
+ * The interface to use for delaying execution, like during retries.
+ *
+ * This is primarily useful for using fake delays in tests.
+ *
+ * Defaults to real execution delays.
+ *
+ * This class takes ownership of the sleeper and closes it when closed.
+ */
+ fun sleeper(sleeper: Sleeper) = apply { this.sleeper = PhantomReachableSleeper(sleeper) }
+
+ /**
+ * The clock to use for operations that require timing, like retries.
+ *
+ * This is primarily useful for using a fake clock in tests.
+ *
+ * Defaults to [Clock.systemUTC].
+ */
+ fun clock(clock: Clock) = apply { this.clock = clock }
+ /**
+ * The base URL to use for every request.
+ *
+ * Defaults to the production environment: `https://api.arcade.dev`.
+ */
+ fun baseUrl(baseUrl: String?) = apply { this.baseUrl = baseUrl }
+
+ /** Alias for calling [Builder.baseUrl] with `baseUrl.orElse(null)`. */
+ fun baseUrl(baseUrl: Optional) = baseUrl(baseUrl.getOrNull())
+
+ /**
+ * Whether to call `validate` on every response before returning it.
+ *
+ * Defaults to false, which means the shape of the response will not be validated upfront.
+ * Instead, validation will only occur for the parts of the response that are accessed.
+ */
fun responseValidation(responseValidation: Boolean) = apply {
this.responseValidation = responseValidation
}
+ /**
+ * Sets the maximum time allowed for various parts of an HTTP call's lifecycle, excluding
+ * retries.
+ *
+ * Defaults to [Timeout.default].
+ */
+ fun timeout(timeout: Timeout) = apply { this.timeout = timeout }
+
+ /**
+ * Sets the maximum time allowed for a complete HTTP call, not including retries.
+ *
+ * See [Timeout.request] for more details.
+ *
+ * For fine-grained control, pass a [Timeout] object.
+ */
+ fun timeout(timeout: Duration) = timeout(Timeout.builder().request(timeout).build())
+
+ /**
+ * The maximum number of times to retry failed requests, with a short exponential backoff
+ * between requests.
+ *
+ * Only the following error types are retried:
+ * - Connection errors (for example, due to a network connectivity problem)
+ * - 408 Request Timeout
+ * - 409 Conflict
+ * - 429 Rate Limit
+ * - 5xx Internal
+ *
+ * The API may also explicitly instruct the SDK to retry or not retry a request.
+ *
+ * Defaults to 2.
+ */
fun maxRetries(maxRetries: Int) = apply { this.maxRetries = maxRetries }
+ /** API key used for authorization in header */
fun apiKey(apiKey: String) = apply { this.apiKey = apiKey }
fun headers(headers: Headers) = apply {
@@ -157,10 +384,63 @@ private constructor(
fun removeAllQueryParams(keys: Set) = apply { queryParams.removeAll(keys) }
- fun fromEnv() = apply { System.getenv("ARCADE_API_KEY")?.let { apiKey(it) } }
+ fun timeout(): Timeout = timeout
+
+ /**
+ * Updates configuration using system properties and environment variables.
+ *
+ * See this table for the available options:
+ *
+ * |Setter |System property |Environment variable|Required|Default value |
+ * |---------|----------------|--------------------|--------|--------------------------|
+ * |`apiKey` |`arcade.apiKey` |`ARCADE_API_KEY` |true |- |
+ * |`baseUrl`|`arcade.baseUrl`|`ARCADE_BASE_URL` |true |`"https://api.arcade.dev"`|
+ *
+ * System properties take precedence over environment variables.
+ */
+ fun fromEnv() = apply {
+ (System.getProperty("arcade.baseUrl") ?: System.getenv("ARCADE_BASE_URL"))?.let {
+ baseUrl(it)
+ }
+ (System.getProperty("arcade.apiKey") ?: System.getenv("ARCADE_API_KEY"))?.let {
+ apiKey(it)
+ }
+ }
+ /**
+ * Returns an immutable instance of [ClientOptions].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ *
+ * The following fields are required:
+ * ```java
+ * .httpClient()
+ * .apiKey()
+ * ```
+ *
+ * @throws IllegalStateException if any required field is unset.
+ */
fun build(): ClientOptions {
val httpClient = checkRequired("httpClient", httpClient)
+ val streamHandlerExecutor =
+ streamHandlerExecutor
+ ?: PhantomReachableExecutorService(
+ Executors.newCachedThreadPool(
+ object : ThreadFactory {
+
+ private val threadFactory: ThreadFactory =
+ Executors.defaultThreadFactory()
+ private val count = AtomicLong(0)
+
+ override fun newThread(runnable: Runnable): Thread =
+ threadFactory.newThread(runnable).also {
+ it.name =
+ "arcade-stream-handler-thread-${count.getAndIncrement()}"
+ }
+ }
+ )
+ )
+ val sleeper = sleeper ?: PhantomReachableSleeper(DefaultSleeper())
val apiKey = checkRequired("apiKey", apiKey)
val headers = Headers.builder()
@@ -182,22 +462,41 @@ private constructor(
return ClientOptions(
httpClient,
- PhantomReachableClosingHttpClient(
- RetryingHttpClient.builder()
- .httpClient(httpClient)
- .clock(clock)
- .maxRetries(maxRetries)
- .build()
- ),
+ RetryingHttpClient.builder()
+ .httpClient(httpClient)
+ .sleeper(sleeper)
+ .clock(clock)
+ .maxRetries(maxRetries)
+ .build(),
+ checkJacksonVersionCompatibility,
jsonMapper,
+ streamHandlerExecutor,
+ sleeper,
clock,
baseUrl,
headers.build(),
queryParams.build(),
responseValidation,
+ timeout,
maxRetries,
apiKey,
)
}
}
+
+ /**
+ * Closes these client options, relinquishing any underlying resources.
+ *
+ * This is purposefully not inherited from [AutoCloseable] because the client options are
+ * long-lived and usually should not be synchronously closed via try-with-resources.
+ *
+ * It's also usually not necessary to call this method at all. the default client automatically
+ * releases threads and connections if they remain idle, but if you are writing an application
+ * that needs to aggressively release unused resources, then you may call this method.
+ */
+ fun close() {
+ httpClient.close()
+ (streamHandlerExecutor as? ExecutorService)?.shutdown()
+ sleeper.close()
+ }
}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/DefaultSleeper.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/DefaultSleeper.kt
new file mode 100644
index 0000000..75b3383
--- /dev/null
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/DefaultSleeper.kt
@@ -0,0 +1,28 @@
+package dev.arcade.core
+
+import java.time.Duration
+import java.util.Timer
+import java.util.TimerTask
+import java.util.concurrent.CompletableFuture
+
+class DefaultSleeper : Sleeper {
+
+ private val timer = Timer("DefaultSleeper", true)
+
+ override fun sleep(duration: Duration) = Thread.sleep(duration.toMillis())
+
+ override fun sleepAsync(duration: Duration): CompletableFuture {
+ val future = CompletableFuture()
+ timer.schedule(
+ object : TimerTask() {
+ override fun run() {
+ future.complete(null)
+ }
+ },
+ duration.toMillis(),
+ )
+ return future
+ }
+
+ override fun close() = timer.cancel()
+}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/HttpRequestBodies.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/HttpRequestBodies.kt
deleted file mode 100644
index 18cb3ba..0000000
--- a/arcade-java-core/src/main/kotlin/dev/arcade/core/HttpRequestBodies.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-@file:JvmName("HttpRequestBodies")
-
-package dev.arcade.core
-
-import com.fasterxml.jackson.databind.json.JsonMapper
-import dev.arcade.core.http.HttpRequestBody
-import dev.arcade.errors.ArcadeException
-import java.io.ByteArrayOutputStream
-import java.io.OutputStream
-import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder
-
-@JvmSynthetic
-internal inline fun json(jsonMapper: JsonMapper, value: T): HttpRequestBody {
- return object : HttpRequestBody {
- private var cachedBytes: ByteArray? = null
-
- private fun serialize(): ByteArray {
- if (cachedBytes != null) return cachedBytes!!
-
- val buffer = ByteArrayOutputStream()
- try {
- jsonMapper.writeValue(buffer, value)
- cachedBytes = buffer.toByteArray()
- return cachedBytes!!
- } catch (e: Exception) {
- throw ArcadeException("Error writing request", e)
- }
- }
-
- override fun writeTo(outputStream: OutputStream) {
- outputStream.write(serialize())
- }
-
- override fun contentType(): String = "application/json"
-
- override fun contentLength(): Long {
- return serialize().size.toLong()
- }
-
- override fun repeatable(): Boolean = true
-
- override fun close() {}
- }
-}
-
-@JvmSynthetic
-internal fun multipartFormData(
- jsonMapper: JsonMapper,
- parts: Array?>,
-): HttpRequestBody {
- val builder = MultipartEntityBuilder.create()
- parts.forEach { part ->
- if (part?.value != null) {
- when (part.value) {
- is JsonValue -> {
- val buffer = ByteArrayOutputStream()
- try {
- jsonMapper.writeValue(buffer, part.value)
- } catch (e: Exception) {
- throw ArcadeException("Error serializing value to json", e)
- }
- builder.addBinaryBody(
- part.name,
- buffer.toByteArray(),
- part.contentType,
- part.filename,
- )
- }
- is Boolean ->
- builder.addTextBody(
- part.name,
- if (part.value) "true" else "false",
- part.contentType,
- )
- is Int -> builder.addTextBody(part.name, part.value.toString(), part.contentType)
- is Long -> builder.addTextBody(part.name, part.value.toString(), part.contentType)
- is Double -> builder.addTextBody(part.name, part.value.toString(), part.contentType)
- is ByteArray ->
- builder.addBinaryBody(part.name, part.value, part.contentType, part.filename)
- is String -> builder.addTextBody(part.name, part.value, part.contentType)
- is Enum -> builder.addTextBody(part.name, part.value.toString(), part.contentType)
- else ->
- throw IllegalArgumentException(
- "Unsupported content type: ${part.value::class.java.simpleName}"
- )
- }
- }
- }
- val entity = builder.build()
-
- return object : HttpRequestBody {
- override fun writeTo(outputStream: OutputStream) {
- try {
- return entity.writeTo(outputStream)
- } catch (e: Exception) {
- throw ArcadeException("Error writing request", e)
- }
- }
-
- override fun contentType(): String = entity.contentType
-
- override fun contentLength(): Long = -1
-
- override fun repeatable(): Boolean = entity.isRepeatable
-
- override fun close() = entity.close()
- }
-}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/ObjectMappers.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/ObjectMappers.kt
index f255b25..bec522d 100644
--- a/arcade-java-core/src/main/kotlin/dev/arcade/core/ObjectMappers.kt
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/ObjectMappers.kt
@@ -3,23 +3,165 @@
package dev.arcade.core
import com.fasterxml.jackson.annotation.JsonInclude
+import com.fasterxml.jackson.core.JsonGenerator
+import com.fasterxml.jackson.core.JsonParseException
+import com.fasterxml.jackson.core.JsonParser
+import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.DeserializationFeature
+import com.fasterxml.jackson.databind.MapperFeature
import com.fasterxml.jackson.databind.SerializationFeature
-import com.fasterxml.jackson.databind.cfg.CoercionAction.Fail
-import com.fasterxml.jackson.databind.cfg.CoercionInputShape.Integer
+import com.fasterxml.jackson.databind.SerializerProvider
+import com.fasterxml.jackson.databind.cfg.CoercionAction
+import com.fasterxml.jackson.databind.cfg.CoercionInputShape
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.json.JsonMapper
+import com.fasterxml.jackson.databind.module.SimpleModule
+import com.fasterxml.jackson.databind.type.LogicalType
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
-import com.fasterxml.jackson.module.kotlin.jacksonMapperBuilder
+import com.fasterxml.jackson.module.kotlin.kotlinModule
+import java.io.InputStream
+import java.time.DateTimeException
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
+import java.time.temporal.ChronoField
fun jsonMapper(): JsonMapper =
- jacksonMapperBuilder()
+ JsonMapper.builder()
+ .addModule(kotlinModule())
.addModule(Jdk8Module())
.addModule(JavaTimeModule())
+ .addModule(
+ SimpleModule()
+ .addSerializer(InputStreamSerializer)
+ .addDeserializer(LocalDateTime::class.java, LenientLocalDateTimeDeserializer())
+ )
+ .withCoercionConfig(LogicalType.Boolean) {
+ it.setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.String, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Array, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Object, CoercionAction.Fail)
+ }
+ .withCoercionConfig(LogicalType.Integer) {
+ it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.String, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Array, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Object, CoercionAction.Fail)
+ }
+ .withCoercionConfig(LogicalType.Float) {
+ it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.String, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Array, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Object, CoercionAction.Fail)
+ }
+ .withCoercionConfig(LogicalType.Textual) {
+ it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Array, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Object, CoercionAction.Fail)
+ }
+ .withCoercionConfig(LogicalType.Array) {
+ it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.String, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Object, CoercionAction.Fail)
+ }
+ .withCoercionConfig(LogicalType.Collection) {
+ it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.String, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Object, CoercionAction.Fail)
+ }
+ .withCoercionConfig(LogicalType.Map) {
+ it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.String, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Object, CoercionAction.Fail)
+ }
+ .withCoercionConfig(LogicalType.POJO) {
+ it.setCoercion(CoercionInputShape.Boolean, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Integer, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Float, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.String, CoercionAction.Fail)
+ .setCoercion(CoercionInputShape.Array, CoercionAction.Fail)
+ }
.serializationInclusion(JsonInclude.Include.NON_ABSENT)
.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.disable(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
- .withCoercionConfig(String::class.java) { it.setCoercion(Integer, Fail) }
+ .disable(MapperFeature.ALLOW_COERCION_OF_SCALARS)
+ .disable(MapperFeature.AUTO_DETECT_CREATORS)
+ .disable(MapperFeature.AUTO_DETECT_FIELDS)
+ .disable(MapperFeature.AUTO_DETECT_GETTERS)
+ .disable(MapperFeature.AUTO_DETECT_IS_GETTERS)
+ .disable(MapperFeature.AUTO_DETECT_SETTERS)
.build()
+
+/** A serializer that serializes [InputStream] to bytes. */
+private object InputStreamSerializer : BaseSerializer(InputStream::class) {
+
+ private fun readResolve(): Any = InputStreamSerializer
+
+ override fun serialize(
+ value: InputStream?,
+ gen: JsonGenerator?,
+ serializers: SerializerProvider?,
+ ) {
+ if (value == null) {
+ gen?.writeNull()
+ } else {
+ value.use { gen?.writeBinary(it.readBytes()) }
+ }
+ }
+}
+
+/**
+ * A deserializer that can deserialize [LocalDateTime] from datetimes, dates, and zoned datetimes.
+ */
+private class LenientLocalDateTimeDeserializer :
+ StdDeserializer(LocalDateTime::class.java) {
+
+ companion object {
+
+ private val DATE_TIME_FORMATTERS =
+ listOf(
+ DateTimeFormatter.ISO_LOCAL_DATE_TIME,
+ DateTimeFormatter.ISO_LOCAL_DATE,
+ DateTimeFormatter.ISO_ZONED_DATE_TIME,
+ )
+ }
+
+ override fun logicalType(): LogicalType = LogicalType.DateTime
+
+ override fun deserialize(p: JsonParser, context: DeserializationContext?): LocalDateTime {
+ val exceptions = mutableListOf()
+
+ for (formatter in DATE_TIME_FORMATTERS) {
+ try {
+ val temporal = formatter.parse(p.text)
+
+ return when {
+ !temporal.isSupported(ChronoField.HOUR_OF_DAY) ->
+ LocalDate.from(temporal).atStartOfDay()
+ !temporal.isSupported(ChronoField.OFFSET_SECONDS) ->
+ LocalDateTime.from(temporal)
+ else -> ZonedDateTime.from(temporal).toLocalDateTime()
+ }
+ } catch (e: DateTimeException) {
+ exceptions.add(e)
+ }
+ }
+
+ throw JsonParseException(p, "Cannot parse `LocalDateTime` from value: ${p.text}").apply {
+ exceptions.forEach { addSuppressed(it) }
+ }
+ }
+}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/Page.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/Page.kt
new file mode 100644
index 0000000..633fa85
--- /dev/null
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/Page.kt
@@ -0,0 +1,33 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package dev.arcade.core
+
+/**
+ * An interface representing a single page, with items of type [T], from a paginated endpoint
+ * response.
+ *
+ * Implementations of this interface are expected to request additional pages synchronously. For
+ * asynchronous pagination, see the [PageAsync] interface.
+ */
+interface Page {
+
+ /**
+ * Returns whether there's another page after this one.
+ *
+ * The method generally doesn't make requests so the result depends entirely on the data in this
+ * page. If a significant amount of time has passed between requesting this page and calling
+ * this method, then the result could be stale.
+ */
+ fun hasNextPage(): Boolean
+
+ /**
+ * Returns the page after this one by making another request.
+ *
+ * @throws IllegalStateException if it's impossible to get the next page. This exception is
+ * avoidable by calling [hasNextPage] first.
+ */
+ fun nextPage(): Page
+
+ /** Returns the items in this page. */
+ fun items(): List
+}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/PageAsync.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/PageAsync.kt
new file mode 100644
index 0000000..c506a09
--- /dev/null
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/PageAsync.kt
@@ -0,0 +1,35 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package dev.arcade.core
+
+import java.util.concurrent.CompletableFuture
+
+/**
+ * An interface representing a single page, with items of type [T], from a paginated endpoint
+ * response.
+ *
+ * Implementations of this interface are expected to request additional pages asynchronously. For
+ * synchronous pagination, see the [Page] interface.
+ */
+interface PageAsync {
+
+ /**
+ * Returns whether there's another page after this one.
+ *
+ * The method generally doesn't make requests so the result depends entirely on the data in this
+ * page. If a significant amount of time has passed between requesting this page and calling
+ * this method, then the result could be stale.
+ */
+ fun hasNextPage(): Boolean
+
+ /**
+ * Returns the page after this one by making another request.
+ *
+ * @throws IllegalStateException if it's impossible to get the next page. This exception is
+ * avoidable by calling [hasNextPage] first.
+ */
+ fun nextPage(): CompletableFuture>
+
+ /** Returns the items in this page. */
+ fun items(): List
+}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/PhantomReachableExecutorService.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/PhantomReachableExecutorService.kt
new file mode 100644
index 0000000..9af39d7
--- /dev/null
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/PhantomReachableExecutorService.kt
@@ -0,0 +1,58 @@
+package dev.arcade.core
+
+import java.util.concurrent.Callable
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Future
+import java.util.concurrent.TimeUnit
+
+/**
+ * A delegating wrapper around an [ExecutorService] that shuts it down once it's only phantom
+ * reachable.
+ *
+ * This class ensures the [ExecutorService] is shut down even if the user forgets to do it.
+ */
+internal class PhantomReachableExecutorService(private val executorService: ExecutorService) :
+ ExecutorService {
+ init {
+ closeWhenPhantomReachable(this) { executorService.shutdown() }
+ }
+
+ override fun execute(command: Runnable) = executorService.execute(command)
+
+ override fun shutdown() = executorService.shutdown()
+
+ override fun shutdownNow(): MutableList = executorService.shutdownNow()
+
+ override fun isShutdown(): Boolean = executorService.isShutdown
+
+ override fun isTerminated(): Boolean = executorService.isTerminated
+
+ override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean =
+ executorService.awaitTermination(timeout, unit)
+
+ override fun submit(task: Callable): Future = executorService.submit(task)
+
+ override fun submit(task: Runnable, result: T): Future =
+ executorService.submit(task, result)
+
+ override fun submit(task: Runnable): Future<*> = executorService.submit(task)
+
+ override fun invokeAll(
+ tasks: MutableCollection>
+ ): MutableList> = executorService.invokeAll(tasks)
+
+ override fun invokeAll(
+ tasks: MutableCollection>,
+ timeout: Long,
+ unit: TimeUnit,
+ ): MutableList> = executorService.invokeAll(tasks, timeout, unit)
+
+ override fun invokeAny(tasks: MutableCollection>): T =
+ executorService.invokeAny(tasks)
+
+ override fun invokeAny(
+ tasks: MutableCollection>,
+ timeout: Long,
+ unit: TimeUnit,
+ ): T = executorService.invokeAny(tasks, timeout, unit)
+}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/PhantomReachableSleeper.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/PhantomReachableSleeper.kt
new file mode 100644
index 0000000..038b7ae
--- /dev/null
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/PhantomReachableSleeper.kt
@@ -0,0 +1,23 @@
+package dev.arcade.core
+
+import java.time.Duration
+import java.util.concurrent.CompletableFuture
+
+/**
+ * A delegating wrapper around a [Sleeper] that closes it once it's only phantom reachable.
+ *
+ * This class ensures the [Sleeper] is closed even if the user forgets to do it.
+ */
+internal class PhantomReachableSleeper(private val sleeper: Sleeper) : Sleeper {
+
+ init {
+ closeWhenPhantomReachable(this, sleeper)
+ }
+
+ override fun sleep(duration: Duration) = sleeper.sleep(duration)
+
+ override fun sleepAsync(duration: Duration): CompletableFuture =
+ sleeper.sleepAsync(duration)
+
+ override fun close() = sleeper.close()
+}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/Properties.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/Properties.kt
index ed1e1a9..7a0964a 100644
--- a/arcade-java-core/src/main/kotlin/dev/arcade/core/Properties.kt
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/Properties.kt
@@ -2,7 +2,7 @@
package dev.arcade.core
-import java.util.Properties
+import dev.arcade.client.ArcadeClient
fun getOsArch(): String {
val osArch = System.getProperty("os.arch")
@@ -16,7 +16,7 @@ fun getOsArch(): String {
"x86_64" -> "x64"
"arm" -> "arm"
"aarch64" -> "arm64"
- else -> "other:${osArch}"
+ else -> "other:$osArch"
}
}
@@ -30,13 +30,13 @@ fun getOsName(): String {
osName.startsWith("Linux") -> "Linux"
osName.startsWith("Mac OS") -> "MacOS"
osName.startsWith("Windows") -> "Windows"
- else -> "Other:${osName}"
+ else -> "Other:$osName"
}
}
fun getOsVersion(): String = System.getProperty("os.version", "unknown")
fun getPackageVersion(): String =
- Properties::class.java.`package`.implementationVersion ?: "unknown"
+ ArcadeClient::class.java.`package`.implementationVersion ?: "unknown"
fun getJavaVersion(): String = System.getProperty("java.version", "unknown")
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/RequestOptions.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/RequestOptions.kt
index e6ab322..59a7d7f 100644
--- a/arcade-java-core/src/main/kotlin/dev/arcade/core/RequestOptions.kt
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/RequestOptions.kt
@@ -2,13 +2,7 @@ package dev.arcade.core
import java.time.Duration
-class RequestOptions private constructor(val responseValidation: Boolean?, val timeout: Duration?) {
- fun applyDefaults(options: RequestOptions): RequestOptions {
- return RequestOptions(
- responseValidation = this.responseValidation ?: options.responseValidation,
- timeout = this.timeout ?: options.timeout,
- )
- }
+class RequestOptions private constructor(val responseValidation: Boolean?, val timeout: Timeout?) {
companion object {
@@ -16,22 +10,37 @@ class RequestOptions private constructor(val responseValidation: Boolean?, val t
@JvmStatic fun none() = NONE
+ @JvmSynthetic
+ internal fun from(clientOptions: ClientOptions): RequestOptions =
+ builder()
+ .responseValidation(clientOptions.responseValidation)
+ .timeout(clientOptions.timeout)
+ .build()
+
@JvmStatic fun builder() = Builder()
}
+ fun applyDefaults(options: RequestOptions): RequestOptions =
+ RequestOptions(
+ responseValidation = responseValidation ?: options.responseValidation,
+ timeout =
+ if (options.timeout != null && timeout != null) timeout.assign(options.timeout)
+ else timeout ?: options.timeout,
+ )
+
class Builder internal constructor() {
private var responseValidation: Boolean? = null
- private var timeout: Duration? = null
+ private var timeout: Timeout? = null
fun responseValidation(responseValidation: Boolean) = apply {
this.responseValidation = responseValidation
}
- fun timeout(timeout: Duration) = apply { this.timeout = timeout }
+ fun timeout(timeout: Timeout) = apply { this.timeout = timeout }
- fun build(): RequestOptions {
- return RequestOptions(responseValidation, timeout)
- }
+ fun timeout(timeout: Duration) = timeout(Timeout.builder().request(timeout).build())
+
+ fun build(): RequestOptions = RequestOptions(responseValidation, timeout)
}
}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/Sleeper.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/Sleeper.kt
new file mode 100644
index 0000000..4f86b7b
--- /dev/null
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/Sleeper.kt
@@ -0,0 +1,21 @@
+package dev.arcade.core
+
+import java.time.Duration
+import java.util.concurrent.CompletableFuture
+
+/**
+ * An interface for delaying execution for a specified amount of time.
+ *
+ * Useful for testing and cleaning up resources.
+ */
+interface Sleeper : AutoCloseable {
+
+ /** Synchronously pauses execution for the given [duration]. */
+ fun sleep(duration: Duration)
+
+ /** Asynchronously pauses execution for the given [duration]. */
+ fun sleepAsync(duration: Duration): CompletableFuture
+
+ /** Overridden from [AutoCloseable] to not have a checked exception in its signature. */
+ override fun close()
+}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/Timeout.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/Timeout.kt
new file mode 100644
index 0000000..e632215
--- /dev/null
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/Timeout.kt
@@ -0,0 +1,171 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package dev.arcade.core
+
+import java.time.Duration
+import java.util.Objects
+import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
+
+/** A class containing timeouts for various processing phases of a request. */
+class Timeout
+private constructor(
+ private val connect: Duration?,
+ private val read: Duration?,
+ private val write: Duration?,
+ private val request: Duration?,
+) {
+
+ /**
+ * The maximum time allowed to establish a connection with a host.
+ *
+ * A value of [Duration.ZERO] means there's no timeout.
+ *
+ * Defaults to `Duration.ofMinutes(1)`.
+ */
+ fun connect(): Duration = connect ?: Duration.ofMinutes(1)
+
+ /**
+ * The maximum time allowed between two data packets when waiting for the server’s response.
+ *
+ * A value of [Duration.ZERO] means there's no timeout.
+ *
+ * Defaults to `request()`.
+ */
+ fun read(): Duration = read ?: request()
+
+ /**
+ * The maximum time allowed between two data packets when sending the request to the server.
+ *
+ * A value of [Duration.ZERO] means there's no timeout.
+ *
+ * Defaults to `request()`.
+ */
+ fun write(): Duration = write ?: request()
+
+ /**
+ * The maximum time allowed for a complete HTTP call, not including retries.
+ *
+ * This includes resolving DNS, connecting, writing the request body, server processing, as well
+ * as reading the response body.
+ *
+ * A value of [Duration.ZERO] means there's no timeout.
+ *
+ * Defaults to `Duration.ofMinutes(1)`.
+ */
+ fun request(): Duration = request ?: Duration.ofMinutes(1)
+
+ fun toBuilder() = Builder().from(this)
+
+ companion object {
+
+ @JvmStatic fun default() = builder().build()
+
+ /** Returns a mutable builder for constructing an instance of [Timeout]. */
+ @JvmStatic fun builder() = Builder()
+ }
+
+ /** A builder for [Timeout]. */
+ class Builder internal constructor() {
+
+ private var connect: Duration? = null
+ private var read: Duration? = null
+ private var write: Duration? = null
+ private var request: Duration? = null
+
+ @JvmSynthetic
+ internal fun from(timeout: Timeout) = apply {
+ connect = timeout.connect
+ read = timeout.read
+ write = timeout.write
+ request = timeout.request
+ }
+
+ /**
+ * The maximum time allowed to establish a connection with a host.
+ *
+ * A value of [Duration.ZERO] means there's no timeout.
+ *
+ * Defaults to `Duration.ofMinutes(1)`.
+ */
+ fun connect(connect: Duration?) = apply { this.connect = connect }
+
+ /** Alias for calling [Builder.connect] with `connect.orElse(null)`. */
+ fun connect(connect: Optional) = connect(connect.getOrNull())
+
+ /**
+ * The maximum time allowed between two data packets when waiting for the server’s response.
+ *
+ * A value of [Duration.ZERO] means there's no timeout.
+ *
+ * Defaults to `request()`.
+ */
+ fun read(read: Duration?) = apply { this.read = read }
+
+ /** Alias for calling [Builder.read] with `read.orElse(null)`. */
+ fun read(read: Optional) = read(read.getOrNull())
+
+ /**
+ * The maximum time allowed between two data packets when sending the request to the server.
+ *
+ * A value of [Duration.ZERO] means there's no timeout.
+ *
+ * Defaults to `request()`.
+ */
+ fun write(write: Duration?) = apply { this.write = write }
+
+ /** Alias for calling [Builder.write] with `write.orElse(null)`. */
+ fun write(write: Optional) = write(write.getOrNull())
+
+ /**
+ * The maximum time allowed for a complete HTTP call, not including retries.
+ *
+ * This includes resolving DNS, connecting, writing the request body, server processing, as
+ * well as reading the response body.
+ *
+ * A value of [Duration.ZERO] means there's no timeout.
+ *
+ * Defaults to `Duration.ofMinutes(1)`.
+ */
+ fun request(request: Duration?) = apply { this.request = request }
+
+ /** Alias for calling [Builder.request] with `request.orElse(null)`. */
+ fun request(request: Optional) = request(request.getOrNull())
+
+ /**
+ * Returns an immutable instance of [Timeout].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ */
+ fun build(): Timeout = Timeout(connect, read, write, request)
+ }
+
+ @JvmSynthetic
+ internal fun assign(target: Timeout): Timeout =
+ target
+ .toBuilder()
+ .apply {
+ connect?.let(this::connect)
+ read?.let(this::read)
+ write?.let(this::write)
+ request?.let(this::request)
+ }
+ .build()
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) {
+ return true
+ }
+
+ return other is Timeout &&
+ connect == other.connect &&
+ read == other.read &&
+ write == other.write &&
+ request == other.request
+ }
+
+ override fun hashCode(): Int = Objects.hash(connect, read, write, request)
+
+ override fun toString() =
+ "Timeout{connect=$connect, read=$read, write=$write, request=$request}"
+}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/Utils.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/Utils.kt
index 022d964..8c7aa5f 100644
--- a/arcade-java-core/src/main/kotlin/dev/arcade/core/Utils.kt
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/Utils.kt
@@ -5,6 +5,8 @@ package dev.arcade.core
import dev.arcade.errors.ArcadeInvalidDataException
import java.util.Collections
import java.util.SortedMap
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.locks.Lock
@JvmSynthetic
internal fun T?.getOrThrow(name: String): T =
@@ -25,4 +27,89 @@ internal fun , V> SortedMap.toImmutable(): SortedMap> Sequence.allMaxBy(selector: (T) -> R): List {
+ var maxValue: R? = null
+ val maxElements = mutableListOf()
+
+ val iterator = iterator()
+ while (iterator.hasNext()) {
+ val element = iterator.next()
+ val value = selector(element)
+ if (maxValue == null || value > maxValue) {
+ maxValue = value
+ maxElements.clear()
+ maxElements.add(element)
+ } else if (value == maxValue) {
+ maxElements.add(element)
+ }
+ }
+
+ return maxElements
+}
+
+/**
+ * Returns whether [this] is equal to [other].
+ *
+ * This differs from [Object.equals] because it also deeply equates arrays based on their contents,
+ * even when there are arrays directly nested within other arrays.
+ */
+@JvmSynthetic
+internal infix fun Any?.contentEquals(other: Any?): Boolean =
+ arrayOf(this).contentDeepEquals(arrayOf(other))
+
+/**
+ * Returns a hash of the given sequence of [values].
+ *
+ * This differs from [java.util.Objects.hash] because it also deeply hashes arrays based on their
+ * contents, even when there are arrays directly nested within other arrays.
+ */
+@JvmSynthetic internal fun contentHash(vararg values: Any?): Int = values.contentDeepHashCode()
+
+/**
+ * Returns a [String] representation of [this].
+ *
+ * This differs from [Object.toString] because it also deeply stringifies arrays based on their
+ * contents, even when there are arrays directly nested within other arrays.
+ */
+@JvmSynthetic
+internal fun Any?.contentToString(): String {
+ var string = arrayOf(this).contentDeepToString()
+ if (string.startsWith('[')) {
+ string = string.substring(1)
+ }
+ if (string.endsWith(']')) {
+ string = string.substring(0, string.length - 1)
+ }
+ return string
+}
+
internal interface Enum
+
+/**
+ * Executes the given [action] while holding the lock, returning a [CompletableFuture] with the
+ * result.
+ *
+ * @param action The asynchronous action to execute while holding the lock
+ * @return A [CompletableFuture] that completes with the result of the action
+ */
+@JvmSynthetic
+internal fun Lock.withLockAsync(action: () -> CompletableFuture): CompletableFuture {
+ lock()
+ val future =
+ try {
+ action()
+ } catch (e: Throwable) {
+ unlock()
+ throw e
+ }
+ future.whenComplete { _, _ -> unlock() }
+ return future
+}
diff --git a/arcade-java-core/src/main/kotlin/dev/arcade/core/Values.kt b/arcade-java-core/src/main/kotlin/dev/arcade/core/Values.kt
index 62438a3..c20aeef 100644
--- a/arcade-java-core/src/main/kotlin/dev/arcade/core/Values.kt
+++ b/arcade-java-core/src/main/kotlin/dev/arcade/core/Values.kt
@@ -1,8 +1,6 @@
package dev.arcade.core
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside
-import com.fasterxml.jackson.annotation.JsonAutoDetect
-import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.core.JsonGenerator
@@ -27,35 +25,55 @@ import com.fasterxml.jackson.databind.node.JsonNodeType.POJO
import com.fasterxml.jackson.databind.node.JsonNodeType.STRING
import com.fasterxml.jackson.databind.ser.std.NullSerializer
import dev.arcade.errors.ArcadeInvalidDataException
-import java.nio.charset.Charset
+import java.io.InputStream
import java.util.Objects
import java.util.Optional
-import org.apache.hc.core5.http.ContentType
+/**
+ * A class representing a serializable JSON field.
+ *
+ * It can either be a [KnownValue] value of type [T], matching the type the SDK expects, or an
+ * arbitrary JSON value that bypasses the type system (via [JsonValue]).
+ */
@JsonDeserialize(using = JsonField.Deserializer::class)
sealed class JsonField {
+ /**
+ * Returns whether this field is missing, which means it will be omitted from the serialized
+ * JSON entirely.
+ */
fun isMissing(): Boolean = this is JsonMissing
+ /** Whether this field is explicitly set to `null`. */
fun isNull(): Boolean = this is JsonNull
- fun asKnown(): Optional =
- when (this) {
- is KnownValue -> Optional.of(value)
- else -> Optional.empty()
- }
+ /**
+ * Returns an [Optional] containing this field's "known" value, meaning it matches the type the
+ * SDK expects, or an empty [Optional] if this field contains an arbitrary [JsonValue].
+ *
+ * This is the opposite of [asUnknown].
+ */
+ fun asKnown():
+ Optional<
+ // Safe because `Optional` is effectively covariant, but Kotlin doesn't know that.
+ @UnsafeVariance
+ T
+ > = Optional.ofNullable((this as? KnownValue)?.value)
/**
- * If the "known" value (i.e. matching the type that the SDK expects) is returned by the API
- * then this method will return an empty `Optional`, otherwise the returned `Optional` is given
- * a `JsonValue`.
+ * Returns an [Optional] containing this field's arbitrary [JsonValue], meaning it mismatches
+ * the type the SDK expects, or an empty [Optional] if this field contains a "known" value.
+ *
+ * This is the opposite of [asKnown].
*/
- fun asUnknown(): Optional =
- when (this) {
- is JsonValue -> Optional.of(this)
- else -> Optional.empty()
- }
+ fun asUnknown(): Optional = Optional.ofNullable(this as? JsonValue)
+ /**
+ * Returns an [Optional] containing this field's boolean value, or an empty [Optional] if it
+ * doesn't contain a boolean.
+ *
+ * This method checks for both a [KnownValue] containing a boolean and for [JsonBoolean].
+ */
fun asBoolean(): Optional =
when (this) {
is JsonBoolean -> Optional.of(value)
@@ -63,6 +81,12 @@ sealed class JsonField {
else -> Optional.empty()
}
+ /**
+ * Returns an [Optional] containing this field's numerical value, or an empty [Optional] if it
+ * doesn't contain a number.
+ *
+ * This method checks for both a [KnownValue] containing a number and for [JsonNumber].
+ */
fun asNumber(): Optional =
when (this) {
is JsonNumber -> Optional.of(value)
@@ -70,6 +94,12 @@ sealed class JsonField {
else -> Optional.empty()
}
+ /**
+ * Returns an [Optional] containing this field's string value, or an empty [Optional] if it
+ * doesn't contain a string.
+ *
+ * This method checks for both a [KnownValue] containing a string and for [JsonString].
+ */
fun asString(): Optional =
when (this) {
is JsonString -> Optional.of(value)
@@ -80,6 +110,12 @@ sealed class JsonField {
fun asStringOrThrow(): String =
asString().orElseThrow { ArcadeInvalidDataException("Value is not a string") }
+ /**
+ * Returns an [Optional] containing this field's list value, or an empty [Optional] if it
+ * doesn't contain a list.
+ *
+ * This method checks for both a [KnownValue] containing a list and for [JsonArray].
+ */
fun asArray(): Optional> =
when (this) {
is JsonArray -> Optional.of(values)
@@ -98,6 +134,12 @@ sealed class JsonField {
else -> Optional.empty()
}
+ /**
+ * Returns an [Optional] containing this field's map value, or an empty [Optional] if it doesn't
+ * contain a map.
+ *
+ * This method checks for both a [KnownValue] containing a map and for [JsonObject].
+ */
fun asObject(): Optional