diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 71594a4c..94046873 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,9 +17,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- - name: Validate Gradle wrapper
- uses: gradle/actions/wrapper-validation@v3
-
- name: Set up Java
uses: actions/setup-java@v4
with:
@@ -30,7 +27,7 @@ jobs:
cache: gradle
- name: Set up Gradle
- uses: gradle/gradle-build-action@v2
+ uses: gradle/actions/setup-gradle@v4
- name: Run lints
run: ./scripts/lint
@@ -55,4 +52,3 @@ jobs:
- name: Run tests
run: ./scripts/test
-
diff --git a/.github/workflows/publish-sonatype.yml b/.github/workflows/publish-sonatype.yml
index d0246884..a1f76188 100755
--- a/.github/workflows/publish-sonatype.yml
+++ b/.github/workflows/publish-sonatype.yml
@@ -29,11 +29,13 @@ jobs:
uses: gradle/gradle-build-action@v2
- name: Publish to Sonatype
- run: |
+ run: |-
+ 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 publishAndReleaseToMavenCentral --stacktrace -PmavenCentralUsername="$SONATYPE_USERNAME" -PmavenCentralPassword="$SONATYPE_PASSWORD"
env:
SONATYPE_USERNAME: ${{ secrets.BRAINTRUST_SONATYPE_USERNAME || secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.BRAINTRUST_SONATYPE_PASSWORD || secrets.SONATYPE_PASSWORD }}
- GPG_SIGNING_KEY_ID: ${{ secrets.BRAINTRUST_SONATYPE_GPG_SIGNING_KEY_ID || secrets.GPG_SIGNING_KEY_ID }}
GPG_SIGNING_KEY: ${{ secrets.BRAINTRUST_SONATYPE_GPG_SIGNING_KEY || secrets.GPG_SIGNING_KEY }}
GPG_SIGNING_PASSWORD: ${{ secrets.BRAINTRUST_SONATYPE_GPG_SIGNING_PASSWORD || secrets.GPG_SIGNING_PASSWORD }}
\ No newline at end of file
diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml
index 88dee7a9..d9128fbc 100755
--- a/.github/workflows/release-doctor.yml
+++ b/.github/workflows/release-doctor.yml
@@ -20,6 +20,5 @@ jobs:
env:
SONATYPE_USERNAME: ${{ secrets.BRAINTRUST_SONATYPE_USERNAME || secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.BRAINTRUST_SONATYPE_PASSWORD || secrets.SONATYPE_PASSWORD }}
- GPG_SIGNING_KEY_ID: ${{ secrets.BRAINTRUST_SONATYPE_GPG_SIGNING_KEY_ID || secrets.GPG_SIGNING_KEY_ID }}
GPG_SIGNING_KEY: ${{ secrets.BRAINTRUST_SONATYPE_GPG_SIGNING_KEY || secrets.GPG_SIGNING_KEY }}
GPG_SIGNING_PASSWORD: ${{ secrets.BRAINTRUST_SONATYPE_GPG_SIGNING_PASSWORD || secrets.GPG_SIGNING_PASSWORD }}
diff --git a/.gitignore b/.gitignore
index 39c31e3e..4e81838d 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
.prism.log
.gradle
.idea
+.kotlin
build
codegen.log
kls_database.db
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 1b77f506..6538ca91 100755
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.7.0"
+ ".": "0.8.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index ac9200d3..24a7d1d7 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,2 +1,2 @@
-configured_endpoints: 104
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/manugoyal%2Fbraintrust-sdk-9d216c8243fe39ba2ffe3bffaab0dba53f1c04b7216d22f9072f6611233de0c7.yml
+configured_endpoints: 110
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/manugoyal%2Fbraintrust-sdk-f0d64ce0e0efde75f9c171f7f3c3d4a72f00a77abb3bc5a7d65b7be1e715689b.yml
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd0c5a5d..5898a8aa 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,128 @@
# Changelog
+## 0.8.0 (2025-03-18)
+
+Full Changelog: [v0.7.0...v0.8.0](https://github.com/braintrustdata/braintrust-java/compare/v0.7.0...v0.8.0)
+
+### ⚠ BREAKING CHANGES
+
+* **client:** refactor multipart formdata impl ([#132](https://github.com/braintrustdata/braintrust-java/issues/132))
+
+### Features
+
+* **api:** api update ([#64](https://github.com/braintrustdata/braintrust-java/issues/64)) ([f1d956d](https://github.com/braintrustdata/braintrust-java/commit/f1d956dec36db180eba1333303361fe577229fbd))
+* **api:** api update ([#66](https://github.com/braintrustdata/braintrust-java/issues/66)) ([02b0490](https://github.com/braintrustdata/braintrust-java/commit/02b0490c081fc51c4c11b12cf8592dc513ac7a1f))
+* **api:** api update ([#67](https://github.com/braintrustdata/braintrust-java/issues/67)) ([2ca59f7](https://github.com/braintrustdata/braintrust-java/commit/2ca59f78f8e775f10b4c09e040e7f55ec27612b1))
+* **api:** api update ([#68](https://github.com/braintrustdata/braintrust-java/issues/68)) ([288a26d](https://github.com/braintrustdata/braintrust-java/commit/288a26dc55ba27313879a00ace18304408adefe9))
+* **api:** manual updates ([#100](https://github.com/braintrustdata/braintrust-java/issues/100)) ([8aea924](https://github.com/braintrustdata/braintrust-java/commit/8aea924ce9c2992c1f620f63c077de8e508852f6))
+* **api:** manual updates ([#107](https://github.com/braintrustdata/braintrust-java/issues/107)) ([893c7c9](https://github.com/braintrustdata/braintrust-java/commit/893c7c9d9397aa0b5abd0b13f8d3d889aa2c6636))
+* **api:** manual updates ([#148](https://github.com/braintrustdata/braintrust-java/issues/148)) ([03f4bce](https://github.com/braintrustdata/braintrust-java/commit/03f4bce0a262fd275057a74fc5cc6736e9df6a70))
+* **api:** manual updates ([#149](https://github.com/braintrustdata/braintrust-java/issues/149)) ([98e4bf7](https://github.com/braintrustdata/braintrust-java/commit/98e4bf7ddd7fd4709b2c4bc12d6f208316f98ac0))
+* **api:** manual updates ([#150](https://github.com/braintrustdata/braintrust-java/issues/150)) ([be6e2a6](https://github.com/braintrustdata/braintrust-java/commit/be6e2a679a76caf1e7ef03422ef91087a2c75981))
+* **api:** manual updates ([#151](https://github.com/braintrustdata/braintrust-java/issues/151)) ([be0354d](https://github.com/braintrustdata/braintrust-java/commit/be0354dbb77893f24083ea294531d8f7a1e00309))
+* **api:** manual updates ([#91](https://github.com/braintrustdata/braintrust-java/issues/91)) ([5e9b07e](https://github.com/braintrustdata/braintrust-java/commit/5e9b07e8f84aff42457a97000302fa2e890282b2))
+* **api:** manual updates ([#93](https://github.com/braintrustdata/braintrust-java/issues/93)) ([3c635ad](https://github.com/braintrustdata/braintrust-java/commit/3c635adb8c1d2e6e26f20e764ac5b9de50b4f2db))
+* **api:** manual updates ([#95](https://github.com/braintrustdata/braintrust-java/issues/95)) ([0e97915](https://github.com/braintrustdata/braintrust-java/commit/0e97915cf7aaa7f1610d56b7c281c28b84ead0ac))
+* **api:** manual updates ([#96](https://github.com/braintrustdata/braintrust-java/issues/96)) ([711a5bc](https://github.com/braintrustdata/braintrust-java/commit/711a5bcf08739214cb62ef99a8de9e267c7afb34))
+* **api:** manual updates ([#97](https://github.com/braintrustdata/braintrust-java/issues/97)) ([f229777](https://github.com/braintrustdata/braintrust-java/commit/f229777ec23714e3e3adfdb6678cffccb13a89f9))
+* **api:** manual updates ([#98](https://github.com/braintrustdata/braintrust-java/issues/98)) ([d27a782](https://github.com/braintrustdata/braintrust-java/commit/d27a7828e50e8e0c91c6b9a527796d0352a8a554))
+* **api:** manual updates ([#99](https://github.com/braintrustdata/braintrust-java/issues/99)) ([eb3e689](https://github.com/braintrustdata/braintrust-java/commit/eb3e6891d822772b04258b63d3ecb1db96537186))
+* **client:** accept `InputStream` and `Path` for file params ([#136](https://github.com/braintrustdata/braintrust-java/issues/136)) ([92073d0](https://github.com/braintrustdata/braintrust-java/commit/92073d0365dcb479ba2565a41305f56f882fecb3))
+* **client:** add logging when debug env is set ([#106](https://github.com/braintrustdata/braintrust-java/issues/106)) ([0bad87e](https://github.com/braintrustdata/braintrust-java/commit/0bad87e1644242cfdbe21d92bf19d138536900f0))
+* **client:** allow configuring timeouts granularly ([#130](https://github.com/braintrustdata/braintrust-java/issues/130)) ([606ebb7](https://github.com/braintrustdata/braintrust-java/commit/606ebb74dcc339190eeefe2b072f273783445edc))
+* **client:** detect binary incompatible jackson versions ([#137](https://github.com/braintrustdata/braintrust-java/issues/137)) ([de216b5](https://github.com/braintrustdata/braintrust-java/commit/de216b560305cb737647cbc34e7775d84ee0e11e))
+* **client:** get rid of annoying checked exceptions ([#118](https://github.com/braintrustdata/braintrust-java/issues/118)) ([0aabe74](https://github.com/braintrustdata/braintrust-java/commit/0aabe74100f814d12b472e5f4d8344b851804d0a))
+* **client:** support `JsonField#asX()` for known values ([#114](https://github.com/braintrustdata/braintrust-java/issues/114)) ([550e1a8](https://github.com/braintrustdata/braintrust-java/commit/550e1a8e92513d874055b527c8b20188078fe312))
+* **client:** support raw response access ([#131](https://github.com/braintrustdata/braintrust-java/issues/131)) ([cc8b5a5](https://github.com/braintrustdata/braintrust-java/commit/cc8b5a5d66ca1a077e58cbf145e468effbfba2c3))
+* **client:** update enum `asX` methods ([#113](https://github.com/braintrustdata/braintrust-java/issues/113)) ([ed83ac5](https://github.com/braintrustdata/braintrust-java/commit/ed83ac52612873519062919eae567a1e82b3b221))
+* generate and publish docs ([#138](https://github.com/braintrustdata/braintrust-java/issues/138)) ([d951553](https://github.com/braintrustdata/braintrust-java/commit/d9515531787d8f63c33c977118ea6c58378ee7f9))
+
+
+### Bug Fixes
+
+* **client:** add missing `@JvmStatic` ([#124](https://github.com/braintrustdata/braintrust-java/issues/124)) ([c4e0b8a](https://github.com/braintrustdata/braintrust-java/commit/c4e0b8ad6755613242bcc135c36dd7538adb8c99))
+* **client:** mark some request bodies as optional ([#120](https://github.com/braintrustdata/braintrust-java/issues/120)) ([274c95c](https://github.com/braintrustdata/braintrust-java/commit/274c95c6df23554f747392fb51a50f3a327125af))
+
+
+### Chores
+
+* **api:** manual updates ([#72](https://github.com/braintrustdata/braintrust-java/issues/72)) ([b6c9f43](https://github.com/braintrustdata/braintrust-java/commit/b6c9f43a2a14533b88d39666809b5ee7ce670504))
+* **client:** expose `Optional`, not nullable, from `ClientOptions` ([#135](https://github.com/braintrustdata/braintrust-java/issues/135)) ([bd8a24d](https://github.com/braintrustdata/braintrust-java/commit/bd8a24d41ddc414c3797a303c2b48ecd643d772b))
+* **client:** refactor multipart formdata impl ([#132](https://github.com/braintrustdata/braintrust-java/issues/132)) ([a32b711](https://github.com/braintrustdata/braintrust-java/commit/a32b7113952189d2624dcbb534a6da5dc36a3224))
+* **client:** use deep identity methods for primitive array types ([#126](https://github.com/braintrustdata/braintrust-java/issues/126)) ([effc197](https://github.com/braintrustdata/braintrust-java/commit/effc19754eb651f3531959839e9a803d4b056587))
+* **deps:** bump jackson to 2.18.1 ([#101](https://github.com/braintrustdata/braintrust-java/issues/101)) ([1d5c887](https://github.com/braintrustdata/braintrust-java/commit/1d5c887b11473e110a197d004a278f9abf8ed4c4))
+* **docs:** add faq to readme ([#119](https://github.com/braintrustdata/braintrust-java/issues/119)) ([5b4d07f](https://github.com/braintrustdata/braintrust-java/commit/5b4d07fc3da4e77c62bdffddd4f6e215401755b2))
+* **docs:** reorganize readme ([#115](https://github.com/braintrustdata/braintrust-java/issues/115)) ([b65c599](https://github.com/braintrustdata/braintrust-java/commit/b65c5991e1451302fd20fb345d82c89fbd47bc80))
+* **internal:** add `.kotlin` to `.gitignore` ([#139](https://github.com/braintrustdata/braintrust-java/issues/139)) ([1e4c3e5](https://github.com/braintrustdata/braintrust-java/commit/1e4c3e584f71562c26dde78c3366c75e1e5dd8bb))
+* **internal:** add async service tests ([#125](https://github.com/braintrustdata/braintrust-java/issues/125)) ([5dc06c8](https://github.com/braintrustdata/braintrust-java/commit/5dc06c8acd281951e841146b1b16803f017c9948))
+* **internal:** add generated comment ([#154](https://github.com/braintrustdata/braintrust-java/issues/154)) ([28a3568](https://github.com/braintrustdata/braintrust-java/commit/28a35683de525b8de0ca345bb246ed537104788d))
+* **internal:** codegen related update ([#105](https://github.com/braintrustdata/braintrust-java/issues/105)) ([ad0fed3](https://github.com/braintrustdata/braintrust-java/commit/ad0fed3824772a29ce635f672c2feb6f7c2beb17))
+* **internal:** codegen related update ([#109](https://github.com/braintrustdata/braintrust-java/issues/109)) ([a8b7cd7](https://github.com/braintrustdata/braintrust-java/commit/a8b7cd7d1e5346b702483b336f9a1d9f71f38c11))
+* **internal:** codegen related update ([#111](https://github.com/braintrustdata/braintrust-java/issues/111)) ([029cbc7](https://github.com/braintrustdata/braintrust-java/commit/029cbc78bf94c707da26f3550db354480909bd78))
+* **internal:** codegen related update ([#117](https://github.com/braintrustdata/braintrust-java/issues/117)) ([aaaef18](https://github.com/braintrustdata/braintrust-java/commit/aaaef188d4fbe8ba97fcc55fe1dac15d527cf1ec))
+* **internal:** codegen related update ([#122](https://github.com/braintrustdata/braintrust-java/issues/122)) ([c9d3840](https://github.com/braintrustdata/braintrust-java/commit/c9d3840c4fded6c0ece296ceb60a5526c3d81c75))
+* **internal:** codegen related update ([#123](https://github.com/braintrustdata/braintrust-java/issues/123)) ([9dbc537](https://github.com/braintrustdata/braintrust-java/commit/9dbc537a6271bc2bbd2839515f77eeb58951e4ab))
+* **internal:** codegen related update ([#145](https://github.com/braintrustdata/braintrust-java/issues/145)) ([23f0da4](https://github.com/braintrustdata/braintrust-java/commit/23f0da4fecedf41ae2a2f573ed0f8e25938057cf))
+* **internal:** codegen related update ([#146](https://github.com/braintrustdata/braintrust-java/issues/146)) ([4a82e6f](https://github.com/braintrustdata/braintrust-java/commit/4a82e6f3159a2b6697f8dbe23304a693e0448ebb))
+* **internal:** don't use `JvmOverloads` in interfaces ([84daad7](https://github.com/braintrustdata/braintrust-java/commit/84daad738819f265ff28ab6c351008a7018e0340))
+* **internal:** get rid of configuration cache ([#116](https://github.com/braintrustdata/braintrust-java/issues/116)) ([1a69b73](https://github.com/braintrustdata/braintrust-java/commit/1a69b73fdc5eb657e72fd3b3d8d3c4f34378cbc3))
+* **internal:** improve sync service tests ([5dc06c8](https://github.com/braintrustdata/braintrust-java/commit/5dc06c8acd281951e841146b1b16803f017c9948))
+* **internal:** make body class constructors private ([b417ac7](https://github.com/braintrustdata/braintrust-java/commit/b417ac7d07cc916a3928a17a57406b08ed014fa0))
+* **internal:** make body classes for multipart requests ([b417ac7](https://github.com/braintrustdata/braintrust-java/commit/b417ac7d07cc916a3928a17a57406b08ed014fa0))
+* **internal:** make test classes internal ([#153](https://github.com/braintrustdata/braintrust-java/issues/153)) ([f4d9990](https://github.com/braintrustdata/braintrust-java/commit/f4d99903225690cfb6fad53ac26fee5c3615977f))
+* **internal:** misc formatting changes ([b417ac7](https://github.com/braintrustdata/braintrust-java/commit/b417ac7d07cc916a3928a17a57406b08ed014fa0))
+* **internal:** reenable warnings as errors ([#141](https://github.com/braintrustdata/braintrust-java/issues/141)) ([84daad7](https://github.com/braintrustdata/braintrust-java/commit/84daad738819f265ff28ab6c351008a7018e0340))
+* **internal:** refactor `ErrorHandlingTest` ([#129](https://github.com/braintrustdata/braintrust-java/issues/129)) ([f61300b](https://github.com/braintrustdata/braintrust-java/commit/f61300b640f71f7d5ce0dee685b0d0524da59d08))
+* **internal:** refactor `PhantomReachableClosingAsyncStreamResponse` impl ([#110](https://github.com/braintrustdata/braintrust-java/issues/110)) ([d1647df](https://github.com/braintrustdata/braintrust-java/commit/d1647dff5977260e2346ad1eeaa5105cc82f8463))
+* **internal:** refactor `ServiceParamsTest` ([#127](https://github.com/braintrustdata/braintrust-java/issues/127)) ([4104744](https://github.com/braintrustdata/braintrust-java/commit/410474405bf5b56695aea53e7d560d7cc48bc144))
+* **internal:** refactor query param serialization impl and tests ([#156](https://github.com/braintrustdata/braintrust-java/issues/156)) ([f141195](https://github.com/braintrustdata/braintrust-java/commit/f1411950e6f4807ac2ec5f34ed12ecbc70b51fad))
+* **internal:** remove unnecessary non-null asserts in tests ([274c95c](https://github.com/braintrustdata/braintrust-java/commit/274c95c6df23554f747392fb51a50f3a327125af))
+* **internal:** remove unused script ([#147](https://github.com/braintrustdata/braintrust-java/issues/147)) ([6eee272](https://github.com/braintrustdata/braintrust-java/commit/6eee2722878aa0bfd85f8f11b6a99fb5aa2b4036))
+* **internal:** rename internal body classes ([b417ac7](https://github.com/braintrustdata/braintrust-java/commit/b417ac7d07cc916a3928a17a57406b08ed014fa0))
+* **internal:** update some formatting in `Values.kt` ([550e1a8](https://github.com/braintrustdata/braintrust-java/commit/550e1a8e92513d874055b527c8b20188078fe312))
+* **internal:** update variable names in tests ([#142](https://github.com/braintrustdata/braintrust-java/issues/142)) ([e8882a7](https://github.com/braintrustdata/braintrust-java/commit/e8882a710bff95c91accdcd7bd293f24a724fb7f))
+* **internal:** use `assertNotNull` in tests for type narrowing ([274c95c](https://github.com/braintrustdata/braintrust-java/commit/274c95c6df23554f747392fb51a50f3a327125af))
+* **internal:** use `getOrNull` instead of `orElse(null)` ([#140](https://github.com/braintrustdata/braintrust-java/issues/140)) ([1db71f8](https://github.com/braintrustdata/braintrust-java/commit/1db71f8bc37728717ecbfcffb31f6b3afbe4380a))
+* **internal:** use better test example values ([#112](https://github.com/braintrustdata/braintrust-java/issues/112)) ([b417ac7](https://github.com/braintrustdata/braintrust-java/commit/b417ac7d07cc916a3928a17a57406b08ed014fa0))
+* rebuild project due to codegen change ([#69](https://github.com/braintrustdata/braintrust-java/issues/69)) ([a7c0606](https://github.com/braintrustdata/braintrust-java/commit/a7c0606dca2139ba230c5d4813dcf76f1ec59d08))
+* rebuild project due to codegen change ([#70](https://github.com/braintrustdata/braintrust-java/issues/70)) ([ccb8dd5](https://github.com/braintrustdata/braintrust-java/commit/ccb8dd583d9783e0466503fa848a1ef5fd577e20))
+* rebuild project due to codegen change ([#71](https://github.com/braintrustdata/braintrust-java/issues/71)) ([0c35214](https://github.com/braintrustdata/braintrust-java/commit/0c35214c2e1b903f741cf39c6a4d7289b13b6271))
+* rebuild project due to codegen change ([#73](https://github.com/braintrustdata/braintrust-java/issues/73)) ([a7a6379](https://github.com/braintrustdata/braintrust-java/commit/a7a6379e2b4e7a8944aa89df8de05ca116db8af8))
+* rebuild project due to codegen change ([#74](https://github.com/braintrustdata/braintrust-java/issues/74)) ([d665d0f](https://github.com/braintrustdata/braintrust-java/commit/d665d0f6cfd44d1bee4ea5dd02d00c0a63203b0c))
+* rebuild project due to codegen change ([#75](https://github.com/braintrustdata/braintrust-java/issues/75)) ([9ac5669](https://github.com/braintrustdata/braintrust-java/commit/9ac566913ab54368b5e1eb10c5e6c20fc86ff75a))
+* rebuild project due to codegen change ([#76](https://github.com/braintrustdata/braintrust-java/issues/76)) ([6498c99](https://github.com/braintrustdata/braintrust-java/commit/6498c997bc0fb2ae68e842ab6b69110115fa458b))
+* rebuild project due to codegen change ([#77](https://github.com/braintrustdata/braintrust-java/issues/77)) ([7c9b2ad](https://github.com/braintrustdata/braintrust-java/commit/7c9b2adb46f8e24d9a3c908400596d96795b8ede))
+* rebuild project due to codegen change ([#78](https://github.com/braintrustdata/braintrust-java/issues/78)) ([fa5ee1f](https://github.com/braintrustdata/braintrust-java/commit/fa5ee1f34562b8ac88122f183ee307aec99ff496))
+* rebuild project due to codegen change ([#79](https://github.com/braintrustdata/braintrust-java/issues/79)) ([9e17de0](https://github.com/braintrustdata/braintrust-java/commit/9e17de04225a4a334fe068f5cbd7e3116c765782))
+* rebuild project due to codegen change ([#80](https://github.com/braintrustdata/braintrust-java/issues/80)) ([223ed2e](https://github.com/braintrustdata/braintrust-java/commit/223ed2e21d18538cff3875f3697b2c0ded1b4ad3))
+* rebuild project due to codegen change ([#81](https://github.com/braintrustdata/braintrust-java/issues/81)) ([44f5fbe](https://github.com/braintrustdata/braintrust-java/commit/44f5fbe68fcb12f0d08a091c88c39bf87f8855af))
+* rebuild project due to codegen change ([#83](https://github.com/braintrustdata/braintrust-java/issues/83)) ([c9d6eae](https://github.com/braintrustdata/braintrust-java/commit/c9d6eae2658ce543a3ccba996373b923e4371cd7))
+* rebuild project due to codegen change ([#84](https://github.com/braintrustdata/braintrust-java/issues/84)) ([8f2a24b](https://github.com/braintrustdata/braintrust-java/commit/8f2a24b0683f51df845f3a433d66994288149e6a))
+* rebuild project due to codegen change ([#86](https://github.com/braintrustdata/braintrust-java/issues/86)) ([d83611f](https://github.com/braintrustdata/braintrust-java/commit/d83611fa8e3155f295f276573235d1bfd6f68371))
+* rebuild project due to codegen change ([#88](https://github.com/braintrustdata/braintrust-java/issues/88)) ([2e62835](https://github.com/braintrustdata/braintrust-java/commit/2e62835d07807e4e1aa018180fac6ca3de574e8e))
+* rebuild project due to codegen change ([#90](https://github.com/braintrustdata/braintrust-java/issues/90)) ([e353324](https://github.com/braintrustdata/braintrust-java/commit/e35332464b9e4a83bef95f90866c0de79bc8c0ff))
+* rebuild project due to codegen change ([#94](https://github.com/braintrustdata/braintrust-java/issues/94)) ([1cd427d](https://github.com/braintrustdata/braintrust-java/commit/1cd427d2b43bbfb35c1ef70ae0e20ff7c66bb876))
+
+
+### Documentation
+
+* add `build` method comments ([#155](https://github.com/braintrustdata/braintrust-java/issues/155)) ([6b504cb](https://github.com/braintrustdata/braintrust-java/commit/6b504cbb091ddbfff7b2487dd143cab9e3d4da01))
+* add immutability explanation to readme ([#121](https://github.com/braintrustdata/braintrust-java/issues/121)) ([6a15c6f](https://github.com/braintrustdata/braintrust-java/commit/6a15c6f103419b9ebd420a5a40de9b675c5302f0))
+* add more phantom reachability docs ([d1647df](https://github.com/braintrustdata/braintrust-java/commit/d1647dff5977260e2346ad1eeaa5105cc82f8463))
+* add raw response readme documentation ([#133](https://github.com/braintrustdata/braintrust-java/issues/133)) ([711d655](https://github.com/braintrustdata/braintrust-java/commit/711d6554708df00be676d724775d090a8f1f5723))
+* deduplicate and refine comments ([#152](https://github.com/braintrustdata/braintrust-java/issues/152)) ([73fb462](https://github.com/braintrustdata/braintrust-java/commit/73fb462b4574596a30820dbab116ba57fab47460))
+* document `JsonValue` construction in readme ([#144](https://github.com/braintrustdata/braintrust-java/issues/144)) ([33fa7ab](https://github.com/braintrustdata/braintrust-java/commit/33fa7ab33b83c408b14fc770b451ac9bb10bd1ca))
+* note required fields in `builder` javadoc ([#134](https://github.com/braintrustdata/braintrust-java/issues/134)) ([c0d11e5](https://github.com/braintrustdata/braintrust-java/commit/c0d11e5cd44c38cb272cab21f18432e25dc204a0))
+* readme parameter tweaks ([5dc06c8](https://github.com/braintrustdata/braintrust-java/commit/5dc06c8acd281951e841146b1b16803f017c9948))
+* revise readme docs about nested params ([#143](https://github.com/braintrustdata/braintrust-java/issues/143)) ([0a06490](https://github.com/braintrustdata/braintrust-java/commit/0a06490b643d90b0666d2f3505dd20e8d519251c))
+* update URLs from stainlessapi.com to stainless.com ([#128](https://github.com/braintrustdata/braintrust-java/issues/128)) ([57e17de](https://github.com/braintrustdata/braintrust-java/commit/57e17de51ce9372dee4dab1f3ad67a040d7900bb))
+
+
+### Styles
+
+* **internal:** move identity methods to bottom of error class ([#104](https://github.com/braintrustdata/braintrust-java/issues/104)) ([fd04071](https://github.com/braintrustdata/braintrust-java/commit/fd04071cbeb2abb04310bd4389279dbaadb9a302))
+* **internal:** reduce verbosity of identity methods ([#103](https://github.com/braintrustdata/braintrust-java/issues/103)) ([f09c5a0](https://github.com/braintrustdata/braintrust-java/commit/f09c5a01a41e464796e781e9923a96f501786242))
+
## 0.7.0 (2024-10-01)
Full Changelog: [v0.6.0...v0.7.0](https://github.com/braintrustdata/braintrust-java/compare/v0.6.0...v0.7.0)
diff --git a/LICENSE b/LICENSE
index 5e03e95a..f3b9e7e5 100755
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright 2024 Braintrust
+ Copyright 2025 Braintrust
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index 31a23686..99c7eb77 100644
--- a/README.md
+++ b/README.md
@@ -2,179 +2,228 @@
-[](https://central.sonatype.com/artifact/com.braintrustdata.api/braintrust-java/0.7.0)
+[](https://central.sonatype.com/artifact/com.braintrustdata.api/braintrust-java/0.8.0)
+[](https://javadoc.io/doc/com.braintrustdata.api/braintrust-java/0.8.0)
-The Braintrust Java SDK provides convenient access to the Braintrust REST API from applications written in Java. It includes helper classes with helpful types and documentation for every request and response property.
+The Braintrust Java SDK provides convenient access to the Braintrust REST API from applications written in Java.
The Braintrust Java SDK is similar to the Braintrust Kotlin SDK but with minor differences that make it more ergonomic for use in Java, such as `Optional` instead of nullable values, `Stream` instead of `Sequence`, and `CompletableFuture` instead of suspend functions.
-It is generated with [Stainless](https://www.stainlessapi.com/).
+It is generated with [Stainless](https://www.stainless.com/).
-## Documentation
+The REST API documentation can be found on [www.braintrustdata.com](https://www.braintrustdata.com/docs/api/spec). Javadocs are also available on [javadoc.io](https://javadoc.io/doc/com.braintrustdata.api/braintrust-java/0.7.0).
-The REST API documentation can be found on [www.braintrustdata.com](https://www.braintrustdata.com/docs/api/spec).
-
----
-
-## Getting started
-
-### Install dependencies
-
-#### Gradle
+## Installation
+### Gradle
+
```kotlin
-implementation("com.braintrustdata.api:braintrust-java:0.7.0")
+implementation("com.braintrustdata.api:braintrust-java:0.8.0")
```
-#### Maven
+### Maven
```xml
com.braintrustdata.api
braintrust-java
- 0.7.0
+ 0.8.0
```
-### Configure the client
+## Requirements
-Use `BraintrustOkHttpClient.builder()` to configure the client.
+This library requires Java 8 or later.
-Alternately, set the environment with `BRAINTRUST_API_KEY`, and use `BraintrustOkHttpClient.fromEnv()` to read from the environment.
+## Usage
```java
+import com.braintrustdata.api.client.BraintrustClient;
+import com.braintrustdata.api.client.okhttp.BraintrustOkHttpClient;
+import com.braintrustdata.api.models.Project;
+import com.braintrustdata.api.models.ProjectCreateParams;
+
+// Configures using the `BRAINTRUST_API_KEY` environment variable
BraintrustClient client = BraintrustOkHttpClient.fromEnv();
-// Note: you can also call fromEnv() from the client builder, for example if you need to set additional properties
-BraintrustClient client = BraintrustOkHttpClient.builder()
- .fromEnv()
- // ... set properties on the builder
+ProjectCreateParams params = ProjectCreateParams.builder()
+ .name("foobar")
.build();
+Project project = client.projects().create(params);
```
-| Property | Environment variable | Required | Default value |
-| -------- | -------------------- | -------- | ------------- |
-| apiKey | `BRAINTRUST_API_KEY` | false | — |
+## Client configuration
-Read the documentation for more configuration options.
+Configure the client using environment variables:
----
+```java
+import com.braintrustdata.api.client.BraintrustClient;
+import com.braintrustdata.api.client.okhttp.BraintrustOkHttpClient;
-### Example: creating a resource
+// Configures using the `BRAINTRUST_API_KEY` environment variable
+BraintrustClient client = BraintrustOkHttpClient.fromEnv();
+```
-To create a new project, first use the `ProjectCreateParams` builder to specify attributes,
-then pass that to the `create` method of the `projects` service.
+Or manually:
```java
-import com.braintrustdata.api.models.Project;
-import com.braintrustdata.api.models.ProjectCreateParams;
+import com.braintrustdata.api.client.BraintrustClient;
+import com.braintrustdata.api.client.okhttp.BraintrustOkHttpClient;
-ProjectCreateParams params = ProjectCreateParams.builder()
- .name("foobar")
+BraintrustClient client = BraintrustOkHttpClient.builder()
+ .apiKey("My API Key")
.build();
-Project project = client.projects().create(params);
```
-### Example: listing resources
-
-The Braintrust API provides a `list` method to get a paginated list of projects.
-You can retrieve the first page by:
+Or using a combination of the two approaches:
```java
-import com.braintrustdata.api.models.Page;
-import com.braintrustdata.api.models.Project;
+import com.braintrustdata.api.client.BraintrustClient;
+import com.braintrustdata.api.client.okhttp.BraintrustOkHttpClient;
-ProjectListPage page = client.projects().list();
-for (Project project : page.objects()) {
- System.out.println(project);
-}
+BraintrustClient client = BraintrustOkHttpClient.builder()
+ // Configures using the `BRAINTRUST_API_KEY` environment variable
+ .fromEnv()
+ .apiKey("My API Key")
+ .build();
```
-See [Pagination](#pagination) below for more information on transparently working with lists of objects without worrying about fetching each page.
+See this table for the available options:
+
+| Setter | Environment variable | Required | Default value |
+| -------- | -------------------- | -------- | ------------- |
+| `apiKey` | `BRAINTRUST_API_KEY` | false | - |
+
+> [!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.
+
+## Requests and responses
+
+To send a request to the Braintrust 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.
----
+For example, `client.projects().create(...)` should be called with an instance of `ProjectCreateParams`, and it will return an instance of `Project`.
-## Requests
+## Immutability
-### Parameters and bodies
+Each class in the SDK has an associated [builder](https://blogs.oracle.com/javamagazine/post/exploring-joshua-blochs-builder-design-pattern-in-java) or factory method for constructing it.
-To make a request to the Braintrust API, you generally build an instance of the appropriate `Params` class.
+Each class is [immutable](https://docs.oracle.com/javase/tutorial/essential/concurrency/immutable.html) once constructed. If the class has an associated builder, then it has a `toBuilder()` method, which can be used to convert it back to a builder for making a modified copy.
-In [Example: creating a resource](#example-creating-a-resource) above, we used the `ProjectCreateParams.builder()` to pass to
-the `create` method of the `projects` service.
+Because each class is immutable, builder modification will _never_ affect already built class instances.
-Sometimes, the API may support other properties that are not yet supported in the Java SDK types. In that case,
-you can attach them using the `putAdditionalProperty` method.
+## Asynchronous execution
+
+The default client is synchronous. To switch to asynchronous execution, call the `async()` method:
```java
-import com.braintrustdata.api.models.core.JsonValue;
+import com.braintrustdata.api.client.BraintrustClient;
+import com.braintrustdata.api.client.okhttp.BraintrustOkHttpClient;
+import com.braintrustdata.api.models.Project;
+import com.braintrustdata.api.models.ProjectCreateParams;
+import java.util.concurrent.CompletableFuture;
+
+// Configures using the `BRAINTRUST_API_KEY` environment variable
+BraintrustClient client = BraintrustOkHttpClient.fromEnv();
+
ProjectCreateParams params = ProjectCreateParams.builder()
- // ... normal properties
- .putAdditionalProperty("secret_param", JsonValue.from("4242"))
+ .name("foobar")
.build();
+CompletableFuture project = client.async().projects().create(params);
```
-## Responses
+Or create an asynchronous client from the beginning:
-### Response validation
+```java
+import com.braintrustdata.api.client.BraintrustClientAsync;
+import com.braintrustdata.api.client.okhttp.BraintrustOkHttpClientAsync;
+import com.braintrustdata.api.models.Project;
+import com.braintrustdata.api.models.ProjectCreateParams;
+import java.util.concurrent.CompletableFuture;
-When receiving a response, the Braintrust Java SDK will deserialize it into instances of the typed model classes. In rare cases, the API may return a response property that doesn't match the expected Java type. If you directly access the mistaken property, the SDK will throw an unchecked `BraintrustInvalidDataException` at runtime. If you would prefer to check in advance that that response is completely well-typed, call `.validate()` on the returned model.
+// Configures using the `BRAINTRUST_API_KEY` environment variable
+BraintrustClientAsync client = BraintrustOkHttpClientAsync.fromEnv();
-```java
-Project project = client.projects().create().validate();
+ProjectCreateParams params = ProjectCreateParams.builder()
+ .name("foobar")
+ .build();
+CompletableFuture project = client.projects().create(params);
```
-### Response properties as JSON
+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.
-In rare cases, you may want to access the underlying JSON value for a response property rather than using the typed version provided by
-this SDK. Each model property has a corresponding JSON version, with an underscore before the method name, which returns a `JsonField` value.
+To access this data, prefix any HTTP method call on a client or service with `withRawResponse()`:
```java
-JsonField field = responseObj._field();
+import com.braintrustdata.api.core.http.Headers;
+import com.braintrustdata.api.core.http.HttpResponseFor;
+import com.braintrustdata.api.models.Project;
+import com.braintrustdata.api.models.ProjectCreateParams;
-if (field.isMissing()) {
- // Value was not specified in the JSON response
-} else if (field.isNull()) {
- // Value was provided as a literal null
-} else {
- // See if value was provided as a string
- Optional jsonString = field.asString();
+ProjectCreateParams params = ProjectCreateParams.builder()
+ .name("foobar")
+ .build();
+HttpResponseFor project = client.projects().withRawResponse().create(params);
- // If the value given by the API did not match the shape that the SDK expects
- // you can deserialise into a custom type
- MyClass myObj = responseObj._field().asUnknown().orElseThrow().convert(MyClass.class);
-}
+int statusCode = project.statusCode();
+Headers headers = project.headers();
```
-### Additional model properties
-
-Sometimes, the server response may include additional properties that are not yet available in this library's types. You can access them using the model's `_additionalProperties` method:
+You can still deserialize the response into an instance of a Java class if needed:
```java
-JsonValue secret = aISecret._additionalProperties().get("secret_field");
+import com.braintrustdata.api.models.Project;
+
+Project parsedProject = project.parse();
```
----
+## Error handling
+
+The SDK throws custom unchecked exception types:
+
+- [`BraintrustServiceException`](braintrust-java-core/src/main/kotlin/com/braintrustdata/api/errors/BraintrustServiceException.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` |
+
+- [`BraintrustIoException`](braintrust-java-core/src/main/kotlin/com/braintrustdata/api/errors/BraintrustIoException.kt): I/O networking errors.
+
+- [`BraintrustInvalidDataException`](braintrust-java-core/src/main/kotlin/com/braintrustdata/api/errors/BraintrustInvalidDataException.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.
+
+- [`BraintrustException`](braintrust-java-core/src/main/kotlin/com/braintrustdata/api/errors/BraintrustException.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.
## Pagination
-For methods that return a paginated list of results, this library provides convenient ways access
-the results either one page at a time, or item-by-item across all pages.
+For methods that return a paginated list of results, this library provides convenient ways access the results either one page at a time, or item-by-item across all pages.
### Auto-pagination
-To iterate through all results across all pages, you can use `autoPager`,
-which automatically handles fetching more pages for you:
+To iterate through all results across all pages, you can use `autoPager`, which automatically handles fetching more pages for you:
### Synchronous
```java
+import com.braintrustdata.api.models.Project;
+import com.braintrustdata.api.models.ProjectListPage;
+
// As an Iterable:
ProjectListPage page = client.projects().list(params);
for (Project project : page.autoPager()) {
@@ -197,12 +246,12 @@ asyncClient.projects().list(params).autoPager()
### Manual pagination
-If none of the above helpers meet your needs, you can also manually request pages one-by-one.
-A page of results has a `data()` method to fetch the list of objects, as well as top-level
-`response` and other methods to fetch top-level data about the page. It also has methods
-`hasNextPage`, `getNextPage`, and `getNextPageParams` methods to help with pagination.
+If none of the above helpers meet your needs, you can also manually request pages one-by-one. A page of results has a `data()` method to fetch the list of objects, as well as top-level `response` and other methods to fetch top-level data about the page. It also has methods `hasNextPage`, `getNextPage`, and `getNextPageParams` methods to help with pagination.
```java
+import com.braintrustdata.api.models.Project;
+import com.braintrustdata.api.models.ProjectListPage;
+
ProjectListPage page = client.projects().list(params);
while (page != null) {
for (Project project : page.objects()) {
@@ -213,39 +262,44 @@ while (page != null) {
}
```
----
-
-## Error handling
+## Logging
-This library throws exceptions in a single hierarchy for easy handling:
+The SDK uses the standard [OkHttp logging interceptor](https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor).
-- **`BraintrustException`** - Base exception for all exceptions
+Enable logging by setting the `BRAINTRUST_LOG` environment variable to `info`:
- - **`BraintrustServiceException`** - HTTP errors with a well-formed response body we were able to parse. The exception message and the `.debuggingRequestId()` will be set by the server.
+```sh
+$ export BRAINTRUST_LOG=info
+```
- | 400 | BadRequestException |
- | ------ | ----------------------------- |
- | 401 | AuthenticationException |
- | 403 | PermissionDeniedException |
- | 404 | NotFoundException |
- | 422 | UnprocessableEntityException |
- | 429 | RateLimitException |
- | 5xx | InternalServerException |
- | others | UnexpectedStatusCodeException |
+Or to `debug` for more verbose logging:
- - **`BraintrustIoException`** - I/O networking errors
- - **`BraintrustInvalidDataException`** - any other exceptions on the client side, e.g.:
- - We failed to serialize the request body
- - We failed to parse the response body (has access to response code and body)
+```sh
+$ export BRAINTRUST_LOG=debug
+```
## Network options
### Retries
-Requests that experience certain errors are automatically retried 2 times by default, with a short exponential backoff. Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, 429 Rate Limit, and >=500 Internal errors will all be retried by default.
-You can provide a `maxRetries` on the client builder to configure this:
+The SDK automatically retries 2 times by default, with a short exponential backoff.
+
+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 response.
+
+To set a custom number of retries, configure the client using the `maxRetries` method:
```java
+import com.braintrustdata.api.client.BraintrustClient;
+import com.braintrustdata.api.client.okhttp.BraintrustOkHttpClient;
+
BraintrustClient client = BraintrustOkHttpClient.builder()
.fromEnv()
.maxRetries(4)
@@ -254,9 +308,26 @@ BraintrustClient client = BraintrustOkHttpClient.builder()
### Timeouts
-Requests time out after 1 minute by default. You can configure this on the client builder:
+Requests time out after 1 minute by default.
+
+To set a custom timeout, configure the method call using the `timeout` method:
```java
+import com.braintrustdata.api.models.Project;
+import com.braintrustdata.api.models.ProjectCreateParams;
+
+Project project = client.projects().create(
+ params, RequestOptions.builder().timeout(Duration.ofSeconds(30)).build()
+);
+```
+
+Or configure the default for all method calls at the client level:
+
+```java
+import com.braintrustdata.api.client.BraintrustClient;
+import com.braintrustdata.api.client.okhttp.BraintrustOkHttpClient;
+import java.time.Duration;
+
BraintrustClient client = BraintrustOkHttpClient.builder()
.fromEnv()
.timeout(Duration.ofSeconds(30))
@@ -265,53 +336,238 @@ BraintrustClient client = BraintrustOkHttpClient.builder()
### Proxies
-Requests can be routed through a proxy. You can configure this on the client builder:
+To route requests through a proxy, configure the client using the `proxy` method:
```java
+import com.braintrustdata.api.client.BraintrustClient;
+import com.braintrustdata.api.client.okhttp.BraintrustOkHttpClient;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+
BraintrustClient client = BraintrustOkHttpClient.builder()
.fromEnv()
.proxy(new Proxy(
- Type.HTTP,
- new InetSocketAddress("proxy.com", 8080)
+ Proxy.Type.HTTP, new InetSocketAddress(
+ "https://example.com", 8080
+ )
))
.build();
```
-## Making custom/undocumented requests
+## Undocumented API functionality
-This library is typed for convenient access to the documented API. If you need to access undocumented
-params or response properties, the library can still be used.
+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.
-### Undocumented request params
+### Parameters
-To make requests using undocumented parameters, you can provide or override parameters on the params object
-while building it.
+To set undocumented parameters, call the `putAdditionalHeader`, `putAdditionalQueryParam`, or `putAdditionalBodyProperty` methods on any `Params` class:
-```kotlin
-FooCreateParams address = FooCreateParams.builder()
- .id("my_id")
- .putAdditionalProperty("secret_prop", JsonValue.from("hello"))
+```java
+import com.braintrustdata.api.core.JsonValue;
+import com.braintrustdata.api.models.ProjectCreateParams;
+
+ProjectCreateParams params = ProjectCreateParams.builder()
+ .putAdditionalHeader("Secret-Header", "42")
+ .putAdditionalQueryParam("secret_query_param", "42")
+ .putAdditionalBodyProperty("secretProperty", JsonValue.from("42"))
.build();
```
-### Undocumented response properties
+These can be accessed on the built object later using the `_additionalHeaders()`, `_additionalQueryParams()`, and `_additionalBodyProperties()` methods.
+
+To set undocumented parameters on _nested_ headers, query params, or body classes, call the `putAdditionalProperty` method on the nested class:
+
+```java
+import com.braintrustdata.api.core.JsonValue;
+import com.braintrustdata.api.models.ProjectSettings;
+import com.braintrustdata.api.models.ProjectUpdateParams;
+
+ProjectUpdateParams params = ProjectUpdateParams.builder()
+ .settings(ProjectSettings.builder()
+ .putAdditionalProperty("secretProperty", JsonValue.from("42"))
+ .build())
+ .build();
+```
+
+These properties can be accessed on the nested built object later using the `_additionalProperties()` method.
+
+To set a documented parameter or property to an undocumented or not yet supported _value_, pass a [`JsonValue`](braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Values.kt) object to its setter:
+
+```java
+import com.braintrustdata.api.core.JsonValue;
+import com.braintrustdata.api.models.ProjectCreateParams;
+
+ProjectCreateParams params = ProjectCreateParams.builder()
+ .name(JsonValue.from(42))
+ .build();
+```
-To access undocumented response properties, you can use `res._additionalProperties()` on a response object to
-get a map of untyped fields of type `Map`. You can then access fields like
-`._additionalProperties().get("secret_prop").asString()` or use other helpers defined on the `JsonValue` class
-to extract it to a desired type.
+The most straightforward way to create a [`JsonValue`](braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Values.kt) is using its `from(...)` method:
+
+```java
+import com.braintrustdata.api.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
+ )
+));
+```
+
+### Response properties
+
+To access undocumented response properties, call the `_additionalProperties()` method:
+
+```java
+import com.braintrustdata.api.core.JsonValue;
+import java.util.Map;
+
+Map additionalProperties = client.projects().create(params)._additionalProperties();
+JsonValue secretPropertyValue = additionalProperties.get("secretProperty");
+
+String result = secretPropertyValue.accept(new JsonValue.Visitor<>() {
+ @Override
+ public String visitNull() {
+ return "It's null!";
+ }
+
+ @Override
+ public String visitBoolean(boolean value) {
+ return "It's a boolean!";
+ }
+
+ @Override
+ public String visitNumber(Number value) {
+ return "It's a number!";
+ }
+
+ // Other methods include `visitMissing`, `visitString`, `visitArray`, and `visitObject`
+ // The default implementation of each unimplemented method delegates to `visitDefault`, which throws by default, but can also be overridden
+});
+```
+
+To access a property's raw JSON value, which may be undocumented, call its `_` prefixed method:
+
+```java
+import com.braintrustdata.api.core.JsonField;
+import java.util.Optional;
+
+JsonField name = client.projects().create(params)._name();
+
+if (name.isMissing()) {
+ // The property is absent from the JSON response
+} else if (name.isNull()) {
+ // The property was set to literal null
+} else {
+ // Check if value was provided as a string
+ // Other methods include `asNumber()`, `asBoolean()`, etc.
+ Optional jsonString = name.asString();
+
+ // Try to deserialize into a custom type
+ MyClass myObject = name.asUnknown().orElseThrow().convert(MyClass.class);
+}
+```
+
+### Response validation
+
+In rare cases, the API may return a response that doesn't match the expected type. For example, the SDK may expect a property to contain a `String`, but the API could return something else.
+
+By default, the SDK will not throw an exception in this case. It will throw [`BraintrustInvalidDataException`](braintrust-java-core/src/main/kotlin/com/braintrustdata/api/errors/BraintrustInvalidDataException.kt) only if you directly access the property.
+
+If you would prefer to check that the response is completely well-typed upfront, then either call `validate()`:
+
+```java
+import com.braintrustdata.api.models.Project;
+
+Project project = client.projects().create(params).validate();
+```
+
+Or configure the method call to validate the response using the `responseValidation` method:
+
+```java
+import com.braintrustdata.api.models.Project;
+import com.braintrustdata.api.models.ProjectCreateParams;
+
+Project project = client.projects().create(
+ params, RequestOptions.builder().responseValidation(true).build()
+);
+```
+
+Or configure the default for all method calls at the client level:
+
+```java
+import com.braintrustdata.api.client.BraintrustClient;
+import com.braintrustdata.api.client.okhttp.BraintrustOkHttpClient;
+
+BraintrustClient client = BraintrustOkHttpClient.builder()
+ .fromEnv()
+ .responseValidation(true)
+ .build();
+```
+
+## FAQ
+
+### Why don't you use plain `enum` classes?
+
+Java `enum` classes are not trivially [forwards compatible](https://www.stainless.com/blog/making-java-enums-forwards-compatible). Using them in the SDK could cause runtime exceptions if the API is updated to respond with a new enum value.
+
+### Why do you represent fields using `JsonField` instead of just plain `T`?
+
+Using `JsonField` enables a few features:
+
+- Allowing usage of [undocumented API functionality](#undocumented-api-functionality)
+- Lazily [validating the API response against the expected shape](#response-validation)
+- Representing absent vs explicitly null values
+
+### Why don't you use [`data` classes](https://kotlinlang.org/docs/data-classes.html)?
+
+It is not [backwards compatible to add new fields to a data class](https://kotlinlang.org/docs/api-guidelines-backward-compatibility.html#avoid-using-data-classes-in-your-api) and we don't want to introduce a breaking change every time we add a field to a class.
+
+### Why don't you use checked exceptions?
+
+Checked exceptions are widely considered a mistake in the Java programming language. In fact, they were omitted from Kotlin for this reason.
+
+Checked exceptions:
+
+- Are verbose to handle
+- Encourage error handling at the wrong level of abstraction, where nothing can be done about the error
+- Are tedious to propagate due to the [function coloring problem](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function)
+- Don't play well with lambdas (also due to the function coloring problem)
## Semantic versioning
This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:
-1. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals)_.
+1. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_
2. Changes that we do not expect to impact the vast majority of users in practice.
We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.
We are keen for your feedback; please open an [issue](https://www.github.com/braintrustdata/braintrust-java/issues) with questions, bugs, or suggestions.
-
-## Requirements
-
-This library requires Java 8 or later.
diff --git a/SECURITY.md b/SECURITY.md
index 5702c21d..0923aae0 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
@@ -20,7 +20,7 @@ or products provided by Braintrust please follow the respective company's securi
### Braintrust Terms and Policies
-Please contact info-test@braintrustdata.com for any questions or concerns regarding security of our services.
+Please contact info@braintrustdata.com for any questions or concerns regarding security of our services.
---
diff --git a/braintrust-java-client-okhttp/build.gradle.kts b/braintrust-java-client-okhttp/build.gradle.kts
index 8c72bdc3..8408ea37 100755
--- a/braintrust-java-client-okhttp/build.gradle.kts
+++ b/braintrust-java-client-okhttp/build.gradle.kts
@@ -6,10 +6,9 @@ plugins {
dependencies {
api(project(":braintrust-java-core"))
- implementation("com.google.guava:guava:33.0.0-jre")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
+ implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
testImplementation(kotlin("test"))
testImplementation("org.assertj:assertj-core:3.25.3")
- testImplementation("org.slf4j:slf4j-simple:2.0.12")
}
diff --git a/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/BraintrustOkHttpClient.kt b/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/BraintrustOkHttpClient.kt
index ae986d28..3800c0ed 100755
--- a/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/BraintrustOkHttpClient.kt
+++ b/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/BraintrustOkHttpClient.kt
@@ -5,26 +5,32 @@ package com.braintrustdata.api.client.okhttp
import com.braintrustdata.api.client.BraintrustClient
import com.braintrustdata.api.client.BraintrustClientImpl
import com.braintrustdata.api.core.ClientOptions
+import com.braintrustdata.api.core.Timeout
+import com.braintrustdata.api.core.http.Headers
+import com.braintrustdata.api.core.http.QueryParams
import com.fasterxml.jackson.databind.json.JsonMapper
import java.net.Proxy
import java.time.Clock
import java.time.Duration
+import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
class BraintrustOkHttpClient private constructor() {
companion object {
+ /** Returns a mutable builder for constructing an instance of [BraintrustOkHttpClient]. */
@JvmStatic fun builder() = Builder()
@JvmStatic fun fromEnv(): BraintrustClient = builder().fromEnv().build()
}
- class Builder {
+ /** A builder for [BraintrustOkHttpClient]. */
+ class Builder internal constructor() {
private var clientOptions: ClientOptions.Builder = ClientOptions.builder()
private var baseUrl: String = ClientOptions.PRODUCTION_URL
- // default timeout for client is 1 minute
- private var timeout: Duration = Duration.ofSeconds(60)
+ private var timeout: Timeout = Timeout.default()
private var proxy: Proxy? = null
fun baseUrl(baseUrl: String) = apply {
@@ -36,6 +42,8 @@ class BraintrustOkHttpClient private constructor() {
fun clock(clock: Clock) = apply { clientOptions.clock(clock) }
+ fun headers(headers: Headers) = apply { clientOptions.headers(headers) }
+
fun headers(headers: Map>) = apply {
clientOptions.headers(headers)
}
@@ -46,13 +54,87 @@ class BraintrustOkHttpClient private constructor() {
clientOptions.putHeaders(name, values)
}
+ fun putAllHeaders(headers: Headers) = apply { clientOptions.putAllHeaders(headers) }
+
fun putAllHeaders(headers: Map>) = apply {
clientOptions.putAllHeaders(headers)
}
- fun removeHeader(name: String) = apply { clientOptions.removeHeader(name) }
+ fun replaceHeaders(name: String, value: String) = apply {
+ clientOptions.replaceHeaders(name, value)
+ }
+
+ fun replaceHeaders(name: String, values: Iterable) = apply {
+ clientOptions.replaceHeaders(name, values)
+ }
+
+ fun replaceAllHeaders(headers: Headers) = apply { clientOptions.replaceAllHeaders(headers) }
+
+ fun replaceAllHeaders(headers: Map>) = apply {
+ clientOptions.replaceAllHeaders(headers)
+ }
+
+ fun removeHeaders(name: String) = apply { clientOptions.removeHeaders(name) }
+
+ fun removeAllHeaders(names: Set) = apply { clientOptions.removeAllHeaders(names) }
+
+ fun queryParams(queryParams: QueryParams) = apply { clientOptions.queryParams(queryParams) }
+
+ fun queryParams(queryParams: Map>) = apply {
+ clientOptions.queryParams(queryParams)
+ }
+
+ fun putQueryParam(key: String, value: String) = apply {
+ clientOptions.putQueryParam(key, value)
+ }
+
+ fun putQueryParams(key: String, values: Iterable) = apply {
+ clientOptions.putQueryParams(key, values)
+ }
+
+ fun putAllQueryParams(queryParams: QueryParams) = apply {
+ clientOptions.putAllQueryParams(queryParams)
+ }
+
+ fun putAllQueryParams(queryParams: Map>) = apply {
+ clientOptions.putAllQueryParams(queryParams)
+ }
+
+ fun replaceQueryParams(key: String, value: String) = apply {
+ clientOptions.replaceQueryParams(key, value)
+ }
+
+ fun replaceQueryParams(key: String, values: Iterable) = apply {
+ clientOptions.replaceQueryParams(key, values)
+ }
+
+ fun replaceAllQueryParams(queryParams: QueryParams) = apply {
+ clientOptions.replaceAllQueryParams(queryParams)
+ }
- fun timeout(timeout: Duration) = apply { this.timeout = timeout }
+ fun replaceAllQueryParams(queryParams: Map>) = apply {
+ clientOptions.replaceAllQueryParams(queryParams)
+ }
+
+ fun removeQueryParams(key: String) = apply { clientOptions.removeQueryParams(key) }
+
+ fun removeAllQueryParams(keys: Set) = apply {
+ clientOptions.removeAllQueryParams(keys)
+ }
+
+ fun timeout(timeout: Timeout) = apply {
+ clientOptions.timeout(timeout)
+ 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())
fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
@@ -64,10 +146,18 @@ class BraintrustOkHttpClient private constructor() {
fun apiKey(apiKey: String?) = apply { clientOptions.apiKey(apiKey) }
+ /** Alias for calling [Builder.apiKey] with `apiKey.orElse(null)`. */
+ fun apiKey(apiKey: Optional) = apiKey(apiKey.getOrNull())
+
fun fromEnv() = apply { clientOptions.fromEnv() }
- fun build(): BraintrustClient {
- return BraintrustClientImpl(
+ /**
+ * Returns an immutable instance of [BraintrustClient].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ */
+ fun build(): BraintrustClient =
+ BraintrustClientImpl(
clientOptions
.httpClient(
OkHttpClient.builder()
@@ -78,6 +168,5 @@ class BraintrustOkHttpClient private constructor() {
)
.build()
)
- }
}
}
diff --git a/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/BraintrustOkHttpClientAsync.kt b/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/BraintrustOkHttpClientAsync.kt
index d813b989..52efdd0a 100755
--- a/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/BraintrustOkHttpClientAsync.kt
+++ b/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/BraintrustOkHttpClientAsync.kt
@@ -5,26 +5,34 @@ package com.braintrustdata.api.client.okhttp
import com.braintrustdata.api.client.BraintrustClientAsync
import com.braintrustdata.api.client.BraintrustClientAsyncImpl
import com.braintrustdata.api.core.ClientOptions
+import com.braintrustdata.api.core.Timeout
+import com.braintrustdata.api.core.http.Headers
+import com.braintrustdata.api.core.http.QueryParams
import com.fasterxml.jackson.databind.json.JsonMapper
import java.net.Proxy
import java.time.Clock
import java.time.Duration
+import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
class BraintrustOkHttpClientAsync private constructor() {
companion object {
+ /**
+ * Returns a mutable builder for constructing an instance of [BraintrustOkHttpClientAsync].
+ */
@JvmStatic fun builder() = Builder()
@JvmStatic fun fromEnv(): BraintrustClientAsync = builder().fromEnv().build()
}
- class Builder {
+ /** A builder for [BraintrustOkHttpClientAsync]. */
+ class Builder internal constructor() {
private var clientOptions: ClientOptions.Builder = ClientOptions.builder()
private var baseUrl: String = ClientOptions.PRODUCTION_URL
- // default timeout for client is 1 minute
- private var timeout: Duration = Duration.ofSeconds(60)
+ private var timeout: Timeout = Timeout.default()
private var proxy: Proxy? = null
fun baseUrl(baseUrl: String) = apply {
@@ -36,6 +44,8 @@ class BraintrustOkHttpClientAsync private constructor() {
fun clock(clock: Clock) = apply { clientOptions.clock(clock) }
+ fun headers(headers: Headers) = apply { clientOptions.headers(headers) }
+
fun headers(headers: Map>) = apply {
clientOptions.headers(headers)
}
@@ -46,13 +56,87 @@ class BraintrustOkHttpClientAsync private constructor() {
clientOptions.putHeaders(name, values)
}
+ fun putAllHeaders(headers: Headers) = apply { clientOptions.putAllHeaders(headers) }
+
fun putAllHeaders(headers: Map>) = apply {
clientOptions.putAllHeaders(headers)
}
- fun removeHeader(name: String) = apply { clientOptions.removeHeader(name) }
+ fun replaceHeaders(name: String, value: String) = apply {
+ clientOptions.replaceHeaders(name, value)
+ }
+
+ fun replaceHeaders(name: String, values: Iterable) = apply {
+ clientOptions.replaceHeaders(name, values)
+ }
+
+ fun replaceAllHeaders(headers: Headers) = apply { clientOptions.replaceAllHeaders(headers) }
+
+ fun replaceAllHeaders(headers: Map>) = apply {
+ clientOptions.replaceAllHeaders(headers)
+ }
+
+ fun removeHeaders(name: String) = apply { clientOptions.removeHeaders(name) }
+
+ fun removeAllHeaders(names: Set) = apply { clientOptions.removeAllHeaders(names) }
+
+ fun queryParams(queryParams: QueryParams) = apply { clientOptions.queryParams(queryParams) }
+
+ fun queryParams(queryParams: Map>) = apply {
+ clientOptions.queryParams(queryParams)
+ }
+
+ fun putQueryParam(key: String, value: String) = apply {
+ clientOptions.putQueryParam(key, value)
+ }
+
+ fun putQueryParams(key: String, values: Iterable) = apply {
+ clientOptions.putQueryParams(key, values)
+ }
+
+ fun putAllQueryParams(queryParams: QueryParams) = apply {
+ clientOptions.putAllQueryParams(queryParams)
+ }
+
+ fun putAllQueryParams(queryParams: Map>) = apply {
+ clientOptions.putAllQueryParams(queryParams)
+ }
+
+ fun replaceQueryParams(key: String, value: String) = apply {
+ clientOptions.replaceQueryParams(key, value)
+ }
+
+ fun replaceQueryParams(key: String, values: Iterable) = apply {
+ clientOptions.replaceQueryParams(key, values)
+ }
+
+ fun replaceAllQueryParams(queryParams: QueryParams) = apply {
+ clientOptions.replaceAllQueryParams(queryParams)
+ }
- fun timeout(timeout: Duration) = apply { this.timeout = timeout }
+ fun replaceAllQueryParams(queryParams: Map>) = apply {
+ clientOptions.replaceAllQueryParams(queryParams)
+ }
+
+ fun removeQueryParams(key: String) = apply { clientOptions.removeQueryParams(key) }
+
+ fun removeAllQueryParams(keys: Set) = apply {
+ clientOptions.removeAllQueryParams(keys)
+ }
+
+ fun timeout(timeout: Timeout) = apply {
+ clientOptions.timeout(timeout)
+ 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())
fun maxRetries(maxRetries: Int) = apply { clientOptions.maxRetries(maxRetries) }
@@ -64,10 +148,18 @@ class BraintrustOkHttpClientAsync private constructor() {
fun apiKey(apiKey: String?) = apply { clientOptions.apiKey(apiKey) }
+ /** Alias for calling [Builder.apiKey] with `apiKey.orElse(null)`. */
+ fun apiKey(apiKey: Optional) = apiKey(apiKey.getOrNull())
+
fun fromEnv() = apply { clientOptions.fromEnv() }
- fun build(): BraintrustClientAsync {
- return BraintrustClientAsyncImpl(
+ /**
+ * Returns an immutable instance of [BraintrustClientAsync].
+ *
+ * Further updates to this [Builder] will not mutate the returned instance.
+ */
+ fun build(): BraintrustClientAsync =
+ BraintrustClientAsyncImpl(
clientOptions
.httpClient(
OkHttpClient.builder()
@@ -78,6 +170,5 @@ class BraintrustOkHttpClientAsync private constructor() {
)
.build()
)
- }
}
}
diff --git a/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/OkHttpClient.kt b/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/OkHttpClient.kt
index a7ca15a9..49a5df68 100755
--- a/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/OkHttpClient.kt
+++ b/braintrust-java-client-okhttp/src/main/kotlin/com/braintrustdata/api/client/okhttp/OkHttpClient.kt
@@ -1,14 +1,15 @@
package com.braintrustdata.api.client.okhttp
import com.braintrustdata.api.core.RequestOptions
+import com.braintrustdata.api.core.Timeout
+import com.braintrustdata.api.core.checkRequired
+import com.braintrustdata.api.core.http.Headers
import com.braintrustdata.api.core.http.HttpClient
import com.braintrustdata.api.core.http.HttpMethod
import com.braintrustdata.api.core.http.HttpRequest
import com.braintrustdata.api.core.http.HttpRequestBody
import com.braintrustdata.api.core.http.HttpResponse
import com.braintrustdata.api.errors.BraintrustIoException
-import com.google.common.collect.ListMultimap
-import com.google.common.collect.MultimapBuilder
import java.io.IOException
import java.io.InputStream
import java.net.Proxy
@@ -16,7 +17,6 @@ import java.time.Duration
import java.util.concurrent.CompletableFuture
import okhttp3.Call
import okhttp3.Callback
-import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType
@@ -25,28 +25,15 @@ import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
+import okhttp3.logging.HttpLoggingInterceptor
import okio.BufferedSink
class OkHttpClient
private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val baseUrl: HttpUrl) :
HttpClient {
- private fun getClient(requestOptions: RequestOptions): okhttp3.OkHttpClient {
- val timeout = requestOptions.timeout ?: return okHttpClient
- return okHttpClient
- .newBuilder()
- .connectTimeout(timeout)
- .readTimeout(timeout)
- .writeTimeout(timeout)
- .callTimeout(if (timeout.seconds == 0L) timeout else timeout.plusSeconds(30))
- .build()
- }
-
- override fun execute(
- request: HttpRequest,
- requestOptions: RequestOptions,
- ): HttpResponse {
- val call = getClient(requestOptions).newCall(request.toRequest())
+ override fun execute(request: HttpRequest, requestOptions: RequestOptions): HttpResponse {
+ val call = newCall(request, requestOptions)
return try {
call.execute().toResponse()
@@ -65,19 +52,18 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
request.body?.run { future.whenComplete { _, _ -> close() } }
- val call = getClient(requestOptions).newCall(request.toRequest())
-
- call.enqueue(
- object : Callback {
- override fun onResponse(call: Call, response: Response) {
- future.complete(response.toResponse())
- }
+ newCall(request, requestOptions)
+ .enqueue(
+ object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ future.complete(response.toResponse())
+ }
- override fun onFailure(call: Call, e: IOException) {
- future.completeExceptionally(BraintrustIoException("Request failed", e))
+ override fun onFailure(call: Call, e: IOException) {
+ future.completeExceptionally(BraintrustIoException("Request failed", e))
+ }
}
- }
- )
+ )
return future
}
@@ -88,19 +74,71 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
okHttpClient.cache?.close()
}
- private fun HttpRequest.toRequest(): Request {
+ private fun newCall(request: HttpRequest, requestOptions: RequestOptions): Call {
+ val clientBuilder = okHttpClient.newBuilder()
+
+ val logLevel =
+ when (System.getenv("BRAINTRUST_LOG")?.lowercase()) {
+ "info" -> HttpLoggingInterceptor.Level.BASIC
+ "debug" -> HttpLoggingInterceptor.Level.BODY
+ else -> null
+ }
+ if (logLevel != null) {
+ clientBuilder.addNetworkInterceptor(
+ HttpLoggingInterceptor().setLevel(logLevel).apply { redactHeader("Authorization") }
+ )
+ }
+
+ requestOptions.timeout?.let {
+ clientBuilder
+ .connectTimeout(it.connect())
+ .readTimeout(it.read())
+ .writeTimeout(it.write())
+ .callTimeout(it.request())
+ }
+
+ val client = clientBuilder.build()
+ return client.newCall(request.toRequest(client))
+ }
+
+ private fun HttpRequest.toRequest(client: okhttp3.OkHttpClient): Request {
var body: RequestBody? = body?.toRequestBody()
- // OkHttpClient always requires a request body for PUT and POST methods
- if (body == null && (method == HttpMethod.PUT || method == HttpMethod.POST)) {
+ if (body == null && requiresBody(method)) {
body = "".toRequestBody()
}
val builder = Request.Builder().url(toUrl()).method(method.name, body)
- headers.forEach(builder::header)
+ headers.names().forEach { name ->
+ headers.values(name).forEach { builder.header(name, it) }
+ }
+
+ if (
+ !headers.names().contains("X-Stainless-Read-Timeout") && client.readTimeoutMillis != 0
+ ) {
+ builder.header(
+ "X-Stainless-Read-Timeout",
+ Duration.ofMillis(client.readTimeoutMillis.toLong()).seconds.toString(),
+ )
+ }
+ if (!headers.names().contains("X-Stainless-Timeout") && client.callTimeoutMillis != 0) {
+ builder.header(
+ "X-Stainless-Timeout",
+ Duration.ofMillis(client.callTimeoutMillis.toLong()).seconds.toString(),
+ )
+ }
return builder.build()
}
+ /** `OkHttpClient` always requires a request body for some methods. */
+ private fun requiresBody(method: HttpMethod): Boolean =
+ when (method) {
+ HttpMethod.POST,
+ HttpMethod.PUT,
+ HttpMethod.PATCH -> true
+ else -> false
+ }
+
private fun HttpRequest.toUrl(): String {
url?.let {
return it
@@ -108,7 +146,9 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
val builder = baseUrl.newBuilder()
pathSegments.forEach(builder::addPathSegment)
- queryParams.forEach(builder::addQueryParameter)
+ queryParams.keys().forEach { key ->
+ queryParams.values(key).forEach { builder.addQueryParameter(key, it) }
+ }
return builder.toString()
}
@@ -118,21 +158,13 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
val length = contentLength()
return object : RequestBody() {
- override fun contentType(): MediaType? {
- return mediaType
- }
+ override fun contentType(): MediaType? = mediaType
- override fun contentLength(): Long {
- return length
- }
+ override fun contentLength(): Long = length
- override fun isOneShot(): Boolean {
- return !repeatable()
- }
+ override fun isOneShot(): Boolean = !repeatable()
- override fun writeTo(sink: BufferedSink) {
- writeTo(sink.outputStream())
- }
+ override fun writeTo(sink: BufferedSink) = writeTo(sink.outputStream())
}
}
@@ -140,63 +172,50 @@ private constructor(private val okHttpClient: okhttp3.OkHttpClient, private val
val headers = headers.toHeaders()
return object : HttpResponse {
- override fun statusCode(): Int {
- return code
- }
+ override fun statusCode(): Int = code
- override fun headers(): ListMultimap {
- return headers
- }
+ override fun headers(): Headers = headers
- override fun body(): InputStream {
- return body!!.byteStream()
- }
+ override fun body(): InputStream = body!!.byteStream()
- override fun close() {
- body!!.close()
- }
+ override fun close() = body!!.close()
}
}
- private fun Headers.toHeaders(): ListMultimap {
- val headers =
- MultimapBuilder.treeKeys(String.CASE_INSENSITIVE_ORDER)
- .arrayListValues()
- .build()
-
- forEach { pair -> headers.put(pair.first, pair.second) }
-
- return headers
+ private fun okhttp3.Headers.toHeaders(): Headers {
+ val headersBuilder = Headers.builder()
+ forEach { (name, value) -> headersBuilder.put(name, value) }
+ return headersBuilder.build()
}
companion object {
@JvmStatic fun builder() = Builder()
}
- class Builder {
+ class Builder internal constructor() {
private var baseUrl: HttpUrl? = null
- // default timeout is 1 minute
- private var timeout: Duration = Duration.ofSeconds(60)
+ private var timeout: Timeout = Timeout.default()
private var proxy: Proxy? = null
fun baseUrl(baseUrl: String) = apply { this.baseUrl = baseUrl.toHttpUrl() }
- fun timeout(timeout: Duration) = apply { this.timeout = timeout }
+ fun timeout(timeout: Timeout) = apply { this.timeout = timeout }
+
+ fun timeout(timeout: Duration) = timeout(Timeout.builder().request(timeout).build())
fun proxy(proxy: Proxy?) = apply { this.proxy = proxy }
- fun build(): OkHttpClient {
- return OkHttpClient(
+ 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(),
- checkNotNull(baseUrl) { "`baseUrl` is required but was not set" },
+ checkRequired("baseUrl", baseUrl),
)
- }
}
}
diff --git a/braintrust-java-core/build.gradle.kts b/braintrust-java-core/build.gradle.kts
index 8688a46e..4d9c5428 100755
--- a/braintrust-java-core/build.gradle.kts
+++ b/braintrust-java-core/build.gradle.kts
@@ -4,14 +4,14 @@ plugins {
}
dependencies {
- api("com.fasterxml.jackson.core:jackson-core:2.14.3")
- api("com.fasterxml.jackson.core:jackson-databind:2.14.3")
- api("com.google.guava:guava:33.0.0-jre")
+ api("com.fasterxml.jackson.core:jackson-core:2.18.1")
+ api("com.fasterxml.jackson.core:jackson-databind:2.18.1")
+ api("com.google.errorprone:error_prone_annotations:2.33.0")
- implementation("com.fasterxml.jackson.core:jackson-annotations:2.14.3")
- implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.14.3")
- implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.3")
- implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.14.3")
+ 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("org.apache.httpcomponents.core5:httpcore5:5.2.4")
implementation("org.apache.httpcomponents.client5:httpclient5:5.3.1")
@@ -19,8 +19,9 @@ dependencies {
testImplementation(project(":braintrust-java-client-okhttp"))
testImplementation("com.github.tomakehurst:wiremock-jre8:2.35.2")
testImplementation("org.assertj:assertj-core:3.25.3")
- testImplementation("org.assertj:assertj-guava:3.25.3")
- testImplementation("org.slf4j:slf4j-simple:2.0.12")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.3")
+ 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/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClient.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClient.kt
index 3424863b..bc327480 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClient.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClient.kt
@@ -1,16 +1,56 @@
// File generated from our OpenAPI spec by Stainless.
-@file:Suppress("OVERLOADS_INTERFACE") // See https://youtrack.jetbrains.com/issue/KT-36102
-
package com.braintrustdata.api.client
-import com.braintrustdata.api.models.*
-import com.braintrustdata.api.services.blocking.*
-
+import com.braintrustdata.api.services.blocking.AclService
+import com.braintrustdata.api.services.blocking.AiSecretService
+import com.braintrustdata.api.services.blocking.ApiKeyService
+import com.braintrustdata.api.services.blocking.DatasetService
+import com.braintrustdata.api.services.blocking.EnvVarService
+import com.braintrustdata.api.services.blocking.EvalService
+import com.braintrustdata.api.services.blocking.ExperimentService
+import com.braintrustdata.api.services.blocking.FunctionService
+import com.braintrustdata.api.services.blocking.GroupService
+import com.braintrustdata.api.services.blocking.OrganizationService
+import com.braintrustdata.api.services.blocking.ProjectScoreService
+import com.braintrustdata.api.services.blocking.ProjectService
+import com.braintrustdata.api.services.blocking.ProjectTagService
+import com.braintrustdata.api.services.blocking.PromptService
+import com.braintrustdata.api.services.blocking.RoleService
+import com.braintrustdata.api.services.blocking.SpanIframeService
+import com.braintrustdata.api.services.blocking.TopLevelService
+import com.braintrustdata.api.services.blocking.UserService
+import com.braintrustdata.api.services.blocking.ViewService
+
+/**
+ * A client for interacting with the Braintrust REST API synchronously. You can also switch to
+ * asynchronous execution via the [async] method.
+ *
+ * This client performs best when you create a single instance and reuse it for all interactions
+ * with the REST API. This is because each client holds its own connection pool and thread pools.
+ * Reusing connections and threads reduces latency and saves memory. The client also handles rate
+ * limiting per client. This means that creating and using multiple instances at the same time will
+ * not respect rate limits.
+ *
+ * The threads and connections that are held will be released automatically if they remain idle. But
+ * if you are writing an application that needs to aggressively release unused resources, then you
+ * may call [close].
+ */
interface BraintrustClient {
+ /**
+ * Returns a version of this client that uses asynchronous execution.
+ *
+ * The returned client shares its resources, like its connection pool and thread pools, with
+ * this client.
+ */
fun async(): BraintrustClientAsync
+ /**
+ * Returns a view of this service that provides access to raw HTTP responses for each method.
+ */
+ fun withRawResponse(): WithRawResponse
+
fun topLevel(): TopLevelService
fun projects(): ProjectService
@@ -33,6 +73,8 @@ interface BraintrustClient {
fun projectTags(): ProjectTagService
+ fun spanIframes(): SpanIframeService
+
fun functions(): FunctionService
fun views(): ViewService
@@ -46,4 +88,59 @@ interface BraintrustClient {
fun envVars(): EnvVarService
fun evals(): EvalService
+
+ /**
+ * Closes this client, relinquishing any underlying resources.
+ *
+ * This is purposefully not inherited from [AutoCloseable] because the client is 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 HTTP 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()
+
+ /** A view of [BraintrustClient] that provides access to raw HTTP responses for each method. */
+ interface WithRawResponse {
+
+ fun topLevel(): TopLevelService.WithRawResponse
+
+ fun projects(): ProjectService.WithRawResponse
+
+ fun experiments(): ExperimentService.WithRawResponse
+
+ fun datasets(): DatasetService.WithRawResponse
+
+ fun prompts(): PromptService.WithRawResponse
+
+ fun roles(): RoleService.WithRawResponse
+
+ fun groups(): GroupService.WithRawResponse
+
+ fun acls(): AclService.WithRawResponse
+
+ fun users(): UserService.WithRawResponse
+
+ fun projectScores(): ProjectScoreService.WithRawResponse
+
+ fun projectTags(): ProjectTagService.WithRawResponse
+
+ fun spanIframes(): SpanIframeService.WithRawResponse
+
+ fun functions(): FunctionService.WithRawResponse
+
+ fun views(): ViewService.WithRawResponse
+
+ fun organizations(): OrganizationService.WithRawResponse
+
+ fun apiKeys(): ApiKeyService.WithRawResponse
+
+ fun aiSecrets(): AiSecretService.WithRawResponse
+
+ fun envVars(): EnvVarService.WithRawResponse
+
+ fun evals(): EvalService.WithRawResponse
+ }
}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientAsync.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientAsync.kt
index 94511fd9..24ed3368 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientAsync.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientAsync.kt
@@ -1,16 +1,56 @@
// File generated from our OpenAPI spec by Stainless.
-@file:Suppress("OVERLOADS_INTERFACE") // See https://youtrack.jetbrains.com/issue/KT-36102
-
package com.braintrustdata.api.client
-import com.braintrustdata.api.models.*
-import com.braintrustdata.api.services.async.*
-
+import com.braintrustdata.api.services.async.AclServiceAsync
+import com.braintrustdata.api.services.async.AiSecretServiceAsync
+import com.braintrustdata.api.services.async.ApiKeyServiceAsync
+import com.braintrustdata.api.services.async.DatasetServiceAsync
+import com.braintrustdata.api.services.async.EnvVarServiceAsync
+import com.braintrustdata.api.services.async.EvalServiceAsync
+import com.braintrustdata.api.services.async.ExperimentServiceAsync
+import com.braintrustdata.api.services.async.FunctionServiceAsync
+import com.braintrustdata.api.services.async.GroupServiceAsync
+import com.braintrustdata.api.services.async.OrganizationServiceAsync
+import com.braintrustdata.api.services.async.ProjectScoreServiceAsync
+import com.braintrustdata.api.services.async.ProjectServiceAsync
+import com.braintrustdata.api.services.async.ProjectTagServiceAsync
+import com.braintrustdata.api.services.async.PromptServiceAsync
+import com.braintrustdata.api.services.async.RoleServiceAsync
+import com.braintrustdata.api.services.async.SpanIframeServiceAsync
+import com.braintrustdata.api.services.async.TopLevelServiceAsync
+import com.braintrustdata.api.services.async.UserServiceAsync
+import com.braintrustdata.api.services.async.ViewServiceAsync
+
+/**
+ * A client for interacting with the Braintrust REST API asynchronously. You can also switch to
+ * synchronous execution via the [sync] method.
+ *
+ * This client performs best when you create a single instance and reuse it for all interactions
+ * with the REST API. This is because each client holds its own connection pool and thread pools.
+ * Reusing connections and threads reduces latency and saves memory. The client also handles rate
+ * limiting per client. This means that creating and using multiple instances at the same time will
+ * not respect rate limits.
+ *
+ * The threads and connections that are held will be released automatically if they remain idle. But
+ * if you are writing an application that needs to aggressively release unused resources, then you
+ * may call [close].
+ */
interface BraintrustClientAsync {
+ /**
+ * Returns a version of this client that uses synchronous execution.
+ *
+ * The returned client shares its resources, like its connection pool and thread pools, with
+ * this client.
+ */
fun sync(): BraintrustClient
+ /**
+ * Returns a view of this service that provides access to raw HTTP responses for each method.
+ */
+ fun withRawResponse(): WithRawResponse
+
fun topLevel(): TopLevelServiceAsync
fun projects(): ProjectServiceAsync
@@ -33,6 +73,8 @@ interface BraintrustClientAsync {
fun projectTags(): ProjectTagServiceAsync
+ fun spanIframes(): SpanIframeServiceAsync
+
fun functions(): FunctionServiceAsync
fun views(): ViewServiceAsync
@@ -46,4 +88,61 @@ interface BraintrustClientAsync {
fun envVars(): EnvVarServiceAsync
fun evals(): EvalServiceAsync
+
+ /**
+ * Closes this client, relinquishing any underlying resources.
+ *
+ * This is purposefully not inherited from [AutoCloseable] because the client is 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 HTTP 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()
+
+ /**
+ * A view of [BraintrustClientAsync] that provides access to raw HTTP responses for each method.
+ */
+ interface WithRawResponse {
+
+ fun topLevel(): TopLevelServiceAsync.WithRawResponse
+
+ fun projects(): ProjectServiceAsync.WithRawResponse
+
+ fun experiments(): ExperimentServiceAsync.WithRawResponse
+
+ fun datasets(): DatasetServiceAsync.WithRawResponse
+
+ fun prompts(): PromptServiceAsync.WithRawResponse
+
+ fun roles(): RoleServiceAsync.WithRawResponse
+
+ fun groups(): GroupServiceAsync.WithRawResponse
+
+ fun acls(): AclServiceAsync.WithRawResponse
+
+ fun users(): UserServiceAsync.WithRawResponse
+
+ fun projectScores(): ProjectScoreServiceAsync.WithRawResponse
+
+ fun projectTags(): ProjectTagServiceAsync.WithRawResponse
+
+ fun spanIframes(): SpanIframeServiceAsync.WithRawResponse
+
+ fun functions(): FunctionServiceAsync.WithRawResponse
+
+ fun views(): ViewServiceAsync.WithRawResponse
+
+ fun organizations(): OrganizationServiceAsync.WithRawResponse
+
+ fun apiKeys(): ApiKeyServiceAsync.WithRawResponse
+
+ fun aiSecrets(): AiSecretServiceAsync.WithRawResponse
+
+ fun envVars(): EnvVarServiceAsync.WithRawResponse
+
+ fun evals(): EvalServiceAsync.WithRawResponse
+ }
}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientAsyncImpl.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientAsyncImpl.kt
index ee5f3110..e945466b 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientAsyncImpl.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientAsyncImpl.kt
@@ -3,67 +3,133 @@
package com.braintrustdata.api.client
import com.braintrustdata.api.core.ClientOptions
-import com.braintrustdata.api.core.http.HttpResponse.Handler
-import com.braintrustdata.api.errors.BraintrustError
-import com.braintrustdata.api.models.*
-import com.braintrustdata.api.services.async.*
-import com.braintrustdata.api.services.errorHandler
-
-class BraintrustClientAsyncImpl
-constructor(
- private val clientOptions: ClientOptions,
-) : BraintrustClientAsync {
-
- private val errorHandler: Handler = errorHandler(clientOptions.jsonMapper)
-
+import com.braintrustdata.api.core.getPackageVersion
+import com.braintrustdata.api.services.async.AclServiceAsync
+import com.braintrustdata.api.services.async.AclServiceAsyncImpl
+import com.braintrustdata.api.services.async.AiSecretServiceAsync
+import com.braintrustdata.api.services.async.AiSecretServiceAsyncImpl
+import com.braintrustdata.api.services.async.ApiKeyServiceAsync
+import com.braintrustdata.api.services.async.ApiKeyServiceAsyncImpl
+import com.braintrustdata.api.services.async.DatasetServiceAsync
+import com.braintrustdata.api.services.async.DatasetServiceAsyncImpl
+import com.braintrustdata.api.services.async.EnvVarServiceAsync
+import com.braintrustdata.api.services.async.EnvVarServiceAsyncImpl
+import com.braintrustdata.api.services.async.EvalServiceAsync
+import com.braintrustdata.api.services.async.EvalServiceAsyncImpl
+import com.braintrustdata.api.services.async.ExperimentServiceAsync
+import com.braintrustdata.api.services.async.ExperimentServiceAsyncImpl
+import com.braintrustdata.api.services.async.FunctionServiceAsync
+import com.braintrustdata.api.services.async.FunctionServiceAsyncImpl
+import com.braintrustdata.api.services.async.GroupServiceAsync
+import com.braintrustdata.api.services.async.GroupServiceAsyncImpl
+import com.braintrustdata.api.services.async.OrganizationServiceAsync
+import com.braintrustdata.api.services.async.OrganizationServiceAsyncImpl
+import com.braintrustdata.api.services.async.ProjectScoreServiceAsync
+import com.braintrustdata.api.services.async.ProjectScoreServiceAsyncImpl
+import com.braintrustdata.api.services.async.ProjectServiceAsync
+import com.braintrustdata.api.services.async.ProjectServiceAsyncImpl
+import com.braintrustdata.api.services.async.ProjectTagServiceAsync
+import com.braintrustdata.api.services.async.ProjectTagServiceAsyncImpl
+import com.braintrustdata.api.services.async.PromptServiceAsync
+import com.braintrustdata.api.services.async.PromptServiceAsyncImpl
+import com.braintrustdata.api.services.async.RoleServiceAsync
+import com.braintrustdata.api.services.async.RoleServiceAsyncImpl
+import com.braintrustdata.api.services.async.SpanIframeServiceAsync
+import com.braintrustdata.api.services.async.SpanIframeServiceAsyncImpl
+import com.braintrustdata.api.services.async.TopLevelServiceAsync
+import com.braintrustdata.api.services.async.TopLevelServiceAsyncImpl
+import com.braintrustdata.api.services.async.UserServiceAsync
+import com.braintrustdata.api.services.async.UserServiceAsyncImpl
+import com.braintrustdata.api.services.async.ViewServiceAsync
+import com.braintrustdata.api.services.async.ViewServiceAsyncImpl
+
+class BraintrustClientAsyncImpl(private val clientOptions: ClientOptions) : BraintrustClientAsync {
+
+ private val clientOptionsWithUserAgent =
+ if (clientOptions.headers.names().contains("User-Agent")) clientOptions
+ else
+ clientOptions
+ .toBuilder()
+ .putHeader("User-Agent", "${javaClass.simpleName}/Java ${getPackageVersion()}")
+ .build()
+
+ // Pass the original clientOptions so that this client sets its own User-Agent.
private val sync: BraintrustClient by lazy { BraintrustClientImpl(clientOptions) }
- private val topLevel: TopLevelServiceAsync by lazy { TopLevelServiceAsyncImpl(clientOptions) }
+ private val withRawResponse: BraintrustClientAsync.WithRawResponse by lazy {
+ WithRawResponseImpl(clientOptions)
+ }
+
+ private val topLevel: TopLevelServiceAsync by lazy {
+ TopLevelServiceAsyncImpl(clientOptionsWithUserAgent)
+ }
- private val projects: ProjectServiceAsync by lazy { ProjectServiceAsyncImpl(clientOptions) }
+ private val projects: ProjectServiceAsync by lazy {
+ ProjectServiceAsyncImpl(clientOptionsWithUserAgent)
+ }
private val experiments: ExperimentServiceAsync by lazy {
- ExperimentServiceAsyncImpl(clientOptions)
+ ExperimentServiceAsyncImpl(clientOptionsWithUserAgent)
}
- private val datasets: DatasetServiceAsync by lazy { DatasetServiceAsyncImpl(clientOptions) }
+ private val datasets: DatasetServiceAsync by lazy {
+ DatasetServiceAsyncImpl(clientOptionsWithUserAgent)
+ }
- private val prompts: PromptServiceAsync by lazy { PromptServiceAsyncImpl(clientOptions) }
+ private val prompts: PromptServiceAsync by lazy {
+ PromptServiceAsyncImpl(clientOptionsWithUserAgent)
+ }
- private val roles: RoleServiceAsync by lazy { RoleServiceAsyncImpl(clientOptions) }
+ private val roles: RoleServiceAsync by lazy { RoleServiceAsyncImpl(clientOptionsWithUserAgent) }
- private val groups: GroupServiceAsync by lazy { GroupServiceAsyncImpl(clientOptions) }
+ private val groups: GroupServiceAsync by lazy {
+ GroupServiceAsyncImpl(clientOptionsWithUserAgent)
+ }
- private val acls: AclServiceAsync by lazy { AclServiceAsyncImpl(clientOptions) }
+ private val acls: AclServiceAsync by lazy { AclServiceAsyncImpl(clientOptionsWithUserAgent) }
- private val users: UserServiceAsync by lazy { UserServiceAsyncImpl(clientOptions) }
+ private val users: UserServiceAsync by lazy { UserServiceAsyncImpl(clientOptionsWithUserAgent) }
private val projectScores: ProjectScoreServiceAsync by lazy {
- ProjectScoreServiceAsyncImpl(clientOptions)
+ ProjectScoreServiceAsyncImpl(clientOptionsWithUserAgent)
}
private val projectTags: ProjectTagServiceAsync by lazy {
- ProjectTagServiceAsyncImpl(clientOptions)
+ ProjectTagServiceAsyncImpl(clientOptionsWithUserAgent)
}
- private val functions: FunctionServiceAsync by lazy { FunctionServiceAsyncImpl(clientOptions) }
+ private val spanIframes: SpanIframeServiceAsync by lazy {
+ SpanIframeServiceAsyncImpl(clientOptionsWithUserAgent)
+ }
- private val views: ViewServiceAsync by lazy { ViewServiceAsyncImpl(clientOptions) }
+ private val functions: FunctionServiceAsync by lazy {
+ FunctionServiceAsyncImpl(clientOptionsWithUserAgent)
+ }
+
+ private val views: ViewServiceAsync by lazy { ViewServiceAsyncImpl(clientOptionsWithUserAgent) }
private val organizations: OrganizationServiceAsync by lazy {
- OrganizationServiceAsyncImpl(clientOptions)
+ OrganizationServiceAsyncImpl(clientOptionsWithUserAgent)
}
- private val apiKeys: ApiKeyServiceAsync by lazy { ApiKeyServiceAsyncImpl(clientOptions) }
+ private val apiKeys: ApiKeyServiceAsync by lazy {
+ ApiKeyServiceAsyncImpl(clientOptionsWithUserAgent)
+ }
- private val aiSecrets: AiSecretServiceAsync by lazy { AiSecretServiceAsyncImpl(clientOptions) }
+ private val aiSecrets: AiSecretServiceAsync by lazy {
+ AiSecretServiceAsyncImpl(clientOptionsWithUserAgent)
+ }
- private val envVars: EnvVarServiceAsync by lazy { EnvVarServiceAsyncImpl(clientOptions) }
+ private val envVars: EnvVarServiceAsync by lazy {
+ EnvVarServiceAsyncImpl(clientOptionsWithUserAgent)
+ }
- private val evals: EvalServiceAsync by lazy { EvalServiceAsyncImpl(clientOptions) }
+ private val evals: EvalServiceAsync by lazy { EvalServiceAsyncImpl(clientOptionsWithUserAgent) }
override fun sync(): BraintrustClient = sync
+ override fun withRawResponse(): BraintrustClientAsync.WithRawResponse = withRawResponse
+
override fun topLevel(): TopLevelServiceAsync = topLevel
override fun projects(): ProjectServiceAsync = projects
@@ -86,6 +152,8 @@ constructor(
override fun projectTags(): ProjectTagServiceAsync = projectTags
+ override fun spanIframes(): SpanIframeServiceAsync = spanIframes
+
override fun functions(): FunctionServiceAsync = functions
override fun views(): ViewServiceAsync = views
@@ -99,4 +167,124 @@ constructor(
override fun envVars(): EnvVarServiceAsync = envVars
override fun evals(): EvalServiceAsync = evals
+
+ override fun close() = clientOptions.httpClient.close()
+
+ class WithRawResponseImpl internal constructor(private val clientOptions: ClientOptions) :
+ BraintrustClientAsync.WithRawResponse {
+
+ private val topLevel: TopLevelServiceAsync.WithRawResponse by lazy {
+ TopLevelServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val projects: ProjectServiceAsync.WithRawResponse by lazy {
+ ProjectServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val experiments: ExperimentServiceAsync.WithRawResponse by lazy {
+ ExperimentServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val datasets: DatasetServiceAsync.WithRawResponse by lazy {
+ DatasetServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val prompts: PromptServiceAsync.WithRawResponse by lazy {
+ PromptServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val roles: RoleServiceAsync.WithRawResponse by lazy {
+ RoleServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val groups: GroupServiceAsync.WithRawResponse by lazy {
+ GroupServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val acls: AclServiceAsync.WithRawResponse by lazy {
+ AclServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val users: UserServiceAsync.WithRawResponse by lazy {
+ UserServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val projectScores: ProjectScoreServiceAsync.WithRawResponse by lazy {
+ ProjectScoreServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val projectTags: ProjectTagServiceAsync.WithRawResponse by lazy {
+ ProjectTagServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val spanIframes: SpanIframeServiceAsync.WithRawResponse by lazy {
+ SpanIframeServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val functions: FunctionServiceAsync.WithRawResponse by lazy {
+ FunctionServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val views: ViewServiceAsync.WithRawResponse by lazy {
+ ViewServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val organizations: OrganizationServiceAsync.WithRawResponse by lazy {
+ OrganizationServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val apiKeys: ApiKeyServiceAsync.WithRawResponse by lazy {
+ ApiKeyServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val aiSecrets: AiSecretServiceAsync.WithRawResponse by lazy {
+ AiSecretServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val envVars: EnvVarServiceAsync.WithRawResponse by lazy {
+ EnvVarServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val evals: EvalServiceAsync.WithRawResponse by lazy {
+ EvalServiceAsyncImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ override fun topLevel(): TopLevelServiceAsync.WithRawResponse = topLevel
+
+ override fun projects(): ProjectServiceAsync.WithRawResponse = projects
+
+ override fun experiments(): ExperimentServiceAsync.WithRawResponse = experiments
+
+ override fun datasets(): DatasetServiceAsync.WithRawResponse = datasets
+
+ override fun prompts(): PromptServiceAsync.WithRawResponse = prompts
+
+ override fun roles(): RoleServiceAsync.WithRawResponse = roles
+
+ override fun groups(): GroupServiceAsync.WithRawResponse = groups
+
+ override fun acls(): AclServiceAsync.WithRawResponse = acls
+
+ override fun users(): UserServiceAsync.WithRawResponse = users
+
+ override fun projectScores(): ProjectScoreServiceAsync.WithRawResponse = projectScores
+
+ override fun projectTags(): ProjectTagServiceAsync.WithRawResponse = projectTags
+
+ override fun spanIframes(): SpanIframeServiceAsync.WithRawResponse = spanIframes
+
+ override fun functions(): FunctionServiceAsync.WithRawResponse = functions
+
+ override fun views(): ViewServiceAsync.WithRawResponse = views
+
+ override fun organizations(): OrganizationServiceAsync.WithRawResponse = organizations
+
+ override fun apiKeys(): ApiKeyServiceAsync.WithRawResponse = apiKeys
+
+ override fun aiSecrets(): AiSecretServiceAsync.WithRawResponse = aiSecrets
+
+ override fun envVars(): EnvVarServiceAsync.WithRawResponse = envVars
+
+ override fun evals(): EvalServiceAsync.WithRawResponse = evals
+ }
}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientImpl.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientImpl.kt
index e23daeb0..3eecd3cb 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientImpl.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/client/BraintrustClientImpl.kt
@@ -3,63 +3,121 @@
package com.braintrustdata.api.client
import com.braintrustdata.api.core.ClientOptions
-import com.braintrustdata.api.core.http.HttpResponse.Handler
-import com.braintrustdata.api.errors.BraintrustError
-import com.braintrustdata.api.models.*
-import com.braintrustdata.api.services.blocking.*
-import com.braintrustdata.api.services.errorHandler
-
-class BraintrustClientImpl
-constructor(
- private val clientOptions: ClientOptions,
-) : BraintrustClient {
-
- private val errorHandler: Handler = errorHandler(clientOptions.jsonMapper)
-
+import com.braintrustdata.api.core.getPackageVersion
+import com.braintrustdata.api.services.blocking.AclService
+import com.braintrustdata.api.services.blocking.AclServiceImpl
+import com.braintrustdata.api.services.blocking.AiSecretService
+import com.braintrustdata.api.services.blocking.AiSecretServiceImpl
+import com.braintrustdata.api.services.blocking.ApiKeyService
+import com.braintrustdata.api.services.blocking.ApiKeyServiceImpl
+import com.braintrustdata.api.services.blocking.DatasetService
+import com.braintrustdata.api.services.blocking.DatasetServiceImpl
+import com.braintrustdata.api.services.blocking.EnvVarService
+import com.braintrustdata.api.services.blocking.EnvVarServiceImpl
+import com.braintrustdata.api.services.blocking.EvalService
+import com.braintrustdata.api.services.blocking.EvalServiceImpl
+import com.braintrustdata.api.services.blocking.ExperimentService
+import com.braintrustdata.api.services.blocking.ExperimentServiceImpl
+import com.braintrustdata.api.services.blocking.FunctionService
+import com.braintrustdata.api.services.blocking.FunctionServiceImpl
+import com.braintrustdata.api.services.blocking.GroupService
+import com.braintrustdata.api.services.blocking.GroupServiceImpl
+import com.braintrustdata.api.services.blocking.OrganizationService
+import com.braintrustdata.api.services.blocking.OrganizationServiceImpl
+import com.braintrustdata.api.services.blocking.ProjectScoreService
+import com.braintrustdata.api.services.blocking.ProjectScoreServiceImpl
+import com.braintrustdata.api.services.blocking.ProjectService
+import com.braintrustdata.api.services.blocking.ProjectServiceImpl
+import com.braintrustdata.api.services.blocking.ProjectTagService
+import com.braintrustdata.api.services.blocking.ProjectTagServiceImpl
+import com.braintrustdata.api.services.blocking.PromptService
+import com.braintrustdata.api.services.blocking.PromptServiceImpl
+import com.braintrustdata.api.services.blocking.RoleService
+import com.braintrustdata.api.services.blocking.RoleServiceImpl
+import com.braintrustdata.api.services.blocking.SpanIframeService
+import com.braintrustdata.api.services.blocking.SpanIframeServiceImpl
+import com.braintrustdata.api.services.blocking.TopLevelService
+import com.braintrustdata.api.services.blocking.TopLevelServiceImpl
+import com.braintrustdata.api.services.blocking.UserService
+import com.braintrustdata.api.services.blocking.UserServiceImpl
+import com.braintrustdata.api.services.blocking.ViewService
+import com.braintrustdata.api.services.blocking.ViewServiceImpl
+
+class BraintrustClientImpl(private val clientOptions: ClientOptions) : BraintrustClient {
+
+ private val clientOptionsWithUserAgent =
+ if (clientOptions.headers.names().contains("User-Agent")) clientOptions
+ else
+ clientOptions
+ .toBuilder()
+ .putHeader("User-Agent", "${javaClass.simpleName}/Java ${getPackageVersion()}")
+ .build()
+
+ // Pass the original clientOptions so that this client sets its own User-Agent.
private val async: BraintrustClientAsync by lazy { BraintrustClientAsyncImpl(clientOptions) }
- private val topLevel: TopLevelService by lazy { TopLevelServiceImpl(clientOptions) }
+ private val withRawResponse: BraintrustClient.WithRawResponse by lazy {
+ WithRawResponseImpl(clientOptions)
+ }
- private val projects: ProjectService by lazy { ProjectServiceImpl(clientOptions) }
+ private val topLevel: TopLevelService by lazy {
+ TopLevelServiceImpl(clientOptionsWithUserAgent)
+ }
+
+ private val projects: ProjectService by lazy { ProjectServiceImpl(clientOptionsWithUserAgent) }
- private val experiments: ExperimentService by lazy { ExperimentServiceImpl(clientOptions) }
+ private val experiments: ExperimentService by lazy {
+ ExperimentServiceImpl(clientOptionsWithUserAgent)
+ }
- private val datasets: DatasetService by lazy { DatasetServiceImpl(clientOptions) }
+ private val datasets: DatasetService by lazy { DatasetServiceImpl(clientOptionsWithUserAgent) }
- private val prompts: PromptService by lazy { PromptServiceImpl(clientOptions) }
+ private val prompts: PromptService by lazy { PromptServiceImpl(clientOptionsWithUserAgent) }
- private val roles: RoleService by lazy { RoleServiceImpl(clientOptions) }
+ private val roles: RoleService by lazy { RoleServiceImpl(clientOptionsWithUserAgent) }
- private val groups: GroupService by lazy { GroupServiceImpl(clientOptions) }
+ private val groups: GroupService by lazy { GroupServiceImpl(clientOptionsWithUserAgent) }
- private val acls: AclService by lazy { AclServiceImpl(clientOptions) }
+ private val acls: AclService by lazy { AclServiceImpl(clientOptionsWithUserAgent) }
- private val users: UserService by lazy { UserServiceImpl(clientOptions) }
+ private val users: UserService by lazy { UserServiceImpl(clientOptionsWithUserAgent) }
private val projectScores: ProjectScoreService by lazy {
- ProjectScoreServiceImpl(clientOptions)
+ ProjectScoreServiceImpl(clientOptionsWithUserAgent)
}
- private val projectTags: ProjectTagService by lazy { ProjectTagServiceImpl(clientOptions) }
+ private val projectTags: ProjectTagService by lazy {
+ ProjectTagServiceImpl(clientOptionsWithUserAgent)
+ }
+
+ private val spanIframes: SpanIframeService by lazy {
+ SpanIframeServiceImpl(clientOptionsWithUserAgent)
+ }
- private val functions: FunctionService by lazy { FunctionServiceImpl(clientOptions) }
+ private val functions: FunctionService by lazy {
+ FunctionServiceImpl(clientOptionsWithUserAgent)
+ }
- private val views: ViewService by lazy { ViewServiceImpl(clientOptions) }
+ private val views: ViewService by lazy { ViewServiceImpl(clientOptionsWithUserAgent) }
private val organizations: OrganizationService by lazy {
- OrganizationServiceImpl(clientOptions)
+ OrganizationServiceImpl(clientOptionsWithUserAgent)
}
- private val apiKeys: ApiKeyService by lazy { ApiKeyServiceImpl(clientOptions) }
+ private val apiKeys: ApiKeyService by lazy { ApiKeyServiceImpl(clientOptionsWithUserAgent) }
- private val aiSecrets: AiSecretService by lazy { AiSecretServiceImpl(clientOptions) }
+ private val aiSecrets: AiSecretService by lazy {
+ AiSecretServiceImpl(clientOptionsWithUserAgent)
+ }
- private val envVars: EnvVarService by lazy { EnvVarServiceImpl(clientOptions) }
+ private val envVars: EnvVarService by lazy { EnvVarServiceImpl(clientOptionsWithUserAgent) }
- private val evals: EvalService by lazy { EvalServiceImpl(clientOptions) }
+ private val evals: EvalService by lazy { EvalServiceImpl(clientOptionsWithUserAgent) }
override fun async(): BraintrustClientAsync = async
+ override fun withRawResponse(): BraintrustClient.WithRawResponse = withRawResponse
+
override fun topLevel(): TopLevelService = topLevel
override fun projects(): ProjectService = projects
@@ -82,6 +140,8 @@ constructor(
override fun projectTags(): ProjectTagService = projectTags
+ override fun spanIframes(): SpanIframeService = spanIframes
+
override fun functions(): FunctionService = functions
override fun views(): ViewService = views
@@ -95,4 +155,124 @@ constructor(
override fun envVars(): EnvVarService = envVars
override fun evals(): EvalService = evals
+
+ override fun close() = clientOptions.httpClient.close()
+
+ class WithRawResponseImpl internal constructor(private val clientOptions: ClientOptions) :
+ BraintrustClient.WithRawResponse {
+
+ private val topLevel: TopLevelService.WithRawResponse by lazy {
+ TopLevelServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val projects: ProjectService.WithRawResponse by lazy {
+ ProjectServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val experiments: ExperimentService.WithRawResponse by lazy {
+ ExperimentServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val datasets: DatasetService.WithRawResponse by lazy {
+ DatasetServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val prompts: PromptService.WithRawResponse by lazy {
+ PromptServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val roles: RoleService.WithRawResponse by lazy {
+ RoleServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val groups: GroupService.WithRawResponse by lazy {
+ GroupServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val acls: AclService.WithRawResponse by lazy {
+ AclServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val users: UserService.WithRawResponse by lazy {
+ UserServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val projectScores: ProjectScoreService.WithRawResponse by lazy {
+ ProjectScoreServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val projectTags: ProjectTagService.WithRawResponse by lazy {
+ ProjectTagServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val spanIframes: SpanIframeService.WithRawResponse by lazy {
+ SpanIframeServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val functions: FunctionService.WithRawResponse by lazy {
+ FunctionServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val views: ViewService.WithRawResponse by lazy {
+ ViewServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val organizations: OrganizationService.WithRawResponse by lazy {
+ OrganizationServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val apiKeys: ApiKeyService.WithRawResponse by lazy {
+ ApiKeyServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val aiSecrets: AiSecretService.WithRawResponse by lazy {
+ AiSecretServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val envVars: EnvVarService.WithRawResponse by lazy {
+ EnvVarServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ private val evals: EvalService.WithRawResponse by lazy {
+ EvalServiceImpl.WithRawResponseImpl(clientOptions)
+ }
+
+ override fun topLevel(): TopLevelService.WithRawResponse = topLevel
+
+ override fun projects(): ProjectService.WithRawResponse = projects
+
+ override fun experiments(): ExperimentService.WithRawResponse = experiments
+
+ override fun datasets(): DatasetService.WithRawResponse = datasets
+
+ override fun prompts(): PromptService.WithRawResponse = prompts
+
+ override fun roles(): RoleService.WithRawResponse = roles
+
+ override fun groups(): GroupService.WithRawResponse = groups
+
+ override fun acls(): AclService.WithRawResponse = acls
+
+ override fun users(): UserService.WithRawResponse = users
+
+ override fun projectScores(): ProjectScoreService.WithRawResponse = projectScores
+
+ override fun projectTags(): ProjectTagService.WithRawResponse = projectTags
+
+ override fun spanIframes(): SpanIframeService.WithRawResponse = spanIframes
+
+ override fun functions(): FunctionService.WithRawResponse = functions
+
+ override fun views(): ViewService.WithRawResponse = views
+
+ override fun organizations(): OrganizationService.WithRawResponse = organizations
+
+ override fun apiKeys(): ApiKeyService.WithRawResponse = apiKeys
+
+ override fun aiSecrets(): AiSecretService.WithRawResponse = aiSecrets
+
+ override fun envVars(): EnvVarService.WithRawResponse = envVars
+
+ override fun evals(): EvalService.WithRawResponse = evals
+ }
}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/BaseDeserializer.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/BaseDeserializer.kt
index a38e39bd..64d8a5b3 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/BaseDeserializer.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/BaseDeserializer.kt
@@ -18,7 +18,7 @@ abstract class BaseDeserializer(type: KClass) :
override fun createContextual(
context: DeserializationContext,
- property: BeanProperty?
+ property: BeanProperty?,
): JsonDeserializer {
return this
}
@@ -32,7 +32,7 @@ abstract class BaseDeserializer(type: KClass) :
protected fun ObjectCodec.tryDeserialize(
node: JsonNode,
type: TypeReference,
- validate: (T) -> Unit = {}
+ validate: (T) -> Unit = {},
): T? {
return try {
readValue(treeAsTokens(node), type).apply(validate)
@@ -46,7 +46,7 @@ abstract class BaseDeserializer(type: KClass) :
protected fun ObjectCodec.tryDeserialize(
node: JsonNode,
type: JavaType,
- validate: (T) -> Unit = {}
+ validate: (T) -> Unit = {},
): T? {
return try {
readValue(treeAsTokens(node), type).apply(validate)
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Check.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Check.kt
new file mode 100644
index 00000000..4a2305ea
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Check.kt
@@ -0,0 +1,41 @@
+@file:JvmName("Check")
+
+package com.braintrustdata.api.core
+
+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 {
+ check(it.length == length) { "`$name` must have length $length, but was ${it.length}" }
+ }
+
+@JvmSynthetic
+internal fun checkMinLength(name: String, value: String, minLength: Int): String =
+ value.also {
+ check(it.length >= minLength) {
+ if (minLength == 1) "`$name` must be non-empty, but was empty"
+ else "`$name` must have at least length $minLength, but was ${it.length}"
+ }
+ }
+
+@JvmSynthetic
+internal fun checkMaxLength(name: String, value: String, maxLength: Int): String =
+ value.also {
+ check(it.length <= maxLength) {
+ "`$name` must have at most length $maxLength, but was ${it.length}"
+ }
+ }
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/ClientOptions.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/ClientOptions.kt
index a63ea9ec..53b6d493 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/ClientOptions.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/ClientOptions.kt
@@ -2,132 +2,233 @@
package com.braintrustdata.api.core
+import com.braintrustdata.api.core.http.Headers
import com.braintrustdata.api.core.http.HttpClient
+import com.braintrustdata.api.core.http.PhantomReachableClosingHttpClient
+import com.braintrustdata.api.core.http.QueryParams
import com.braintrustdata.api.core.http.RetryingHttpClient
import com.fasterxml.jackson.databind.json.JsonMapper
-import com.google.common.collect.ArrayListMultimap
-import com.google.common.collect.ListMultimap
import java.time.Clock
+import java.util.Optional
+import kotlin.jvm.optionals.getOrNull
class ClientOptions
private constructor(
+ private val originalHttpClient: HttpClient,
@get:JvmName("httpClient") val httpClient: HttpClient,
@get:JvmName("jsonMapper") val jsonMapper: JsonMapper,
@get:JvmName("clock") val clock: Clock,
@get:JvmName("baseUrl") val baseUrl: String,
- @get:JvmName("apiKey") val apiKey: String?,
- @get:JvmName("headers") val headers: ListMultimap,
- @get:JvmName("queryParams") val queryParams: ListMultimap,
+ @get:JvmName("headers") val headers: Headers,
+ @get:JvmName("queryParams") val queryParams: QueryParams,
@get:JvmName("responseValidation") val responseValidation: Boolean,
+ @get:JvmName("timeout") val timeout: Timeout,
+ @get:JvmName("maxRetries") val maxRetries: Int,
+ private val apiKey: String?,
) {
+ fun apiKey(): Optional = Optional.ofNullable(apiKey)
+
+ fun toBuilder() = Builder().from(this)
+
companion object {
const val PRODUCTION_URL = "https://api.braintrust.dev"
+ /**
+ * Returns a mutable builder for constructing an instance of [ClientOptions].
+ *
+ * The following fields are required:
+ * ```java
+ * .httpClient()
+ * ```
+ */
@JvmStatic fun builder() = Builder()
@JvmStatic fun fromEnv(): ClientOptions = builder().fromEnv().build()
}
- class Builder {
+ /** A builder for [ClientOptions]. */
+ class Builder internal constructor() {
private var httpClient: HttpClient? = null
- private var jsonMapper: JsonMapper? = null
+ private var jsonMapper: JsonMapper = jsonMapper()
private var clock: Clock = Clock.systemUTC()
private var baseUrl: String = PRODUCTION_URL
- private var headers: MutableMap> = mutableMapOf()
- private var queryParams: MutableMap> = mutableMapOf()
+ 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
+ jsonMapper = clientOptions.jsonMapper
+ 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 }
fun jsonMapper(jsonMapper: JsonMapper) = apply { this.jsonMapper = jsonMapper }
+ fun clock(clock: Clock) = apply { this.clock = clock }
+
fun baseUrl(baseUrl: String) = apply { this.baseUrl = baseUrl }
- fun clock(clock: Clock) = apply { this.clock = clock }
+ fun responseValidation(responseValidation: Boolean) = apply {
+ this.responseValidation = responseValidation
+ }
+
+ fun timeout(timeout: Timeout) = apply { this.timeout = timeout }
+
+ fun maxRetries(maxRetries: Int) = apply { this.maxRetries = maxRetries }
+
+ fun apiKey(apiKey: String?) = apply { this.apiKey = apiKey }
+
+ /** Alias for calling [Builder.apiKey] with `apiKey.orElse(null)`. */
+ fun apiKey(apiKey: Optional) = apiKey(apiKey.getOrNull())
+
+ fun headers(headers: Headers) = apply {
+ this.headers.clear()
+ putAllHeaders(headers)
+ }
fun headers(headers: Map>) = apply {
this.headers.clear()
putAllHeaders(headers)
}
- fun putHeader(name: String, value: String) = apply {
- this.headers.getOrPut(name) { mutableListOf() }.add(value)
+ fun putHeader(name: String, value: String) = apply { headers.put(name, value) }
+
+ fun putHeaders(name: String, values: Iterable) = apply { headers.put(name, values) }
+
+ fun putAllHeaders(headers: Headers) = apply { this.headers.putAll(headers) }
+
+ fun putAllHeaders(headers: Map>) = apply {
+ this.headers.putAll(headers)
}
- fun putHeaders(name: String, values: Iterable) = apply {
- this.headers.getOrPut(name) { mutableListOf() }.addAll(values)
+ fun replaceHeaders(name: String, value: String) = apply { headers.replace(name, value) }
+
+ fun replaceHeaders(name: String, values: Iterable) = apply {
+ headers.replace(name, values)
}
- fun putAllHeaders(headers: Map>) = apply {
- headers.forEach(this::putHeaders)
+ fun replaceAllHeaders(headers: Headers) = apply { this.headers.replaceAll(headers) }
+
+ fun replaceAllHeaders(headers: Map>) = apply {
+ this.headers.replaceAll(headers)
}
- fun removeHeader(name: String) = apply { this.headers.put(name, mutableListOf()) }
+ fun removeHeaders(name: String) = apply { headers.remove(name) }
+
+ fun removeAllHeaders(names: Set) = apply { headers.removeAll(names) }
+
+ fun queryParams(queryParams: QueryParams) = apply {
+ this.queryParams.clear()
+ putAllQueryParams(queryParams)
+ }
fun queryParams(queryParams: Map>) = apply {
this.queryParams.clear()
putAllQueryParams(queryParams)
}
- fun putQueryParam(name: String, value: String) = apply {
- this.queryParams.getOrPut(name) { mutableListOf() }.add(value)
+ fun putQueryParam(key: String, value: String) = apply { queryParams.put(key, value) }
+
+ fun putQueryParams(key: String, values: Iterable) = apply {
+ queryParams.put(key, values)
}
- fun putQueryParams(name: String, values: Iterable) = apply {
- this.queryParams.getOrPut(name) { mutableListOf() }.addAll(values)
+ fun putAllQueryParams(queryParams: QueryParams) = apply {
+ this.queryParams.putAll(queryParams)
}
fun putAllQueryParams(queryParams: Map>) = apply {
- queryParams.forEach(this::putQueryParams)
+ this.queryParams.putAll(queryParams)
}
- fun removeQueryParam(name: String) = apply { this.queryParams.put(name, mutableListOf()) }
+ fun replaceQueryParams(key: String, value: String) = apply {
+ queryParams.replace(key, value)
+ }
- fun responseValidation(responseValidation: Boolean) = apply {
- this.responseValidation = responseValidation
+ fun replaceQueryParams(key: String, values: Iterable) = apply {
+ queryParams.replace(key, values)
}
- fun maxRetries(maxRetries: Int) = apply { this.maxRetries = maxRetries }
+ fun replaceAllQueryParams(queryParams: QueryParams) = apply {
+ this.queryParams.replaceAll(queryParams)
+ }
- fun apiKey(apiKey: String?) = apply { this.apiKey = apiKey }
+ fun replaceAllQueryParams(queryParams: Map>) = apply {
+ this.queryParams.replaceAll(queryParams)
+ }
+
+ fun removeQueryParams(key: String) = apply { queryParams.remove(key) }
+
+ fun removeAllQueryParams(keys: Set) = apply { queryParams.removeAll(keys) }
fun fromEnv() = apply { System.getenv("BRAINTRUST_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()
+ * ```
+ *
+ * @throws IllegalStateException if any required field is unset.
+ */
fun build(): ClientOptions {
- checkNotNull(httpClient) { "`httpClient` is required but was not set" }
+ val httpClient = checkRequired("httpClient", httpClient)
- val headers = ArrayListMultimap.create()
- val queryParams = ArrayListMultimap.create()
+ val headers = Headers.builder()
+ val queryParams = QueryParams.builder()
headers.put("X-Stainless-Lang", "java")
headers.put("X-Stainless-Arch", getOsArch())
headers.put("X-Stainless-OS", getOsName())
headers.put("X-Stainless-OS-Version", getOsVersion())
headers.put("X-Stainless-Package-Version", getPackageVersion())
+ headers.put("X-Stainless-Runtime", "JRE")
headers.put("X-Stainless-Runtime-Version", getJavaVersion())
- if (!apiKey.isNullOrEmpty()) {
- headers.put("Authorization", "Bearer ${apiKey}")
+ apiKey?.let {
+ if (!it.isEmpty()) {
+ headers.put("Authorization", "Bearer $it")
+ }
}
- this.headers.forEach(headers::replaceValues)
- this.queryParams.forEach(queryParams::replaceValues)
+ headers.replaceAll(this.headers.build())
+ queryParams.replaceAll(this.queryParams.build())
return ClientOptions(
- RetryingHttpClient.builder()
- .httpClient(httpClient!!)
- .clock(clock)
- .maxRetries(maxRetries)
- .build(),
- jsonMapper ?: jsonMapper(),
+ httpClient,
+ PhantomReachableClosingHttpClient(
+ RetryingHttpClient.builder()
+ .httpClient(httpClient)
+ .clock(clock)
+ .maxRetries(maxRetries)
+ .build()
+ ),
+ jsonMapper,
clock,
baseUrl,
- apiKey,
- headers.toUnmodifiable(),
- queryParams.toUnmodifiable(),
+ headers.build(),
+ queryParams.build(),
responseValidation,
+ timeout,
+ maxRetries,
+ apiKey,
)
}
}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/ObjectMappers.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/ObjectMappers.kt
index 5b1f8ea1..588027c6 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/ObjectMappers.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/ObjectMappers.kt
@@ -2,20 +2,29 @@
package com.braintrustdata.api.core
+import com.braintrustdata.api.errors.BraintrustException
+import com.braintrustdata.api.errors.BraintrustInvalidDataException
import com.fasterxml.jackson.annotation.JsonInclude
+import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.SerializationFeature
+import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.cfg.CoercionAction.Fail
import com.fasterxml.jackson.databind.cfg.CoercionInputShape.Integer
+import com.fasterxml.jackson.databind.exc.InvalidDefinitionException
+import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException
import com.fasterxml.jackson.databind.json.JsonMapper
+import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.jacksonMapperBuilder
+import java.io.InputStream
fun jsonMapper(): JsonMapper =
jacksonMapperBuilder()
.addModule(Jdk8Module())
.addModule(JavaTimeModule())
+ .addModule(SimpleModule().addSerializer(InputStreamJsonSerializer))
.serializationInclusion(JsonInclude.Include.NON_ABSENT)
.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.disable(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)
@@ -23,3 +32,53 @@ fun jsonMapper(): JsonMapper =
.disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
.withCoercionConfig(String::class.java) { it.setCoercion(Integer, Fail) }
.build()
+
+private object InputStreamJsonSerializer : BaseSerializer(InputStream::class) {
+
+ override fun serialize(
+ value: InputStream?,
+ gen: JsonGenerator?,
+ serializers: SerializerProvider?,
+ ) {
+ if (value == null) {
+ gen?.writeNull()
+ } else {
+ value.use { gen?.writeBinary(it.readBytes()) }
+ }
+ }
+}
+
+@JvmSynthetic
+internal fun enhanceJacksonException(fallbackMessage: String, e: Exception): Exception {
+ // These exceptions should only happen if our code is wrong OR if the user is using a binary
+ // incompatible version of `com.fasterxml.jackson.core:jackson-databind`:
+ // https://javadoc.io/static/com.fasterxml.jackson.core/jackson-databind/2.18.1/index.html
+ val isUnexpectedException =
+ e is UnrecognizedPropertyException || e is InvalidDefinitionException
+ if (!isUnexpectedException) {
+ return BraintrustInvalidDataException(fallbackMessage, e)
+ }
+
+ val jacksonVersion = JsonMapper::class.java.`package`.implementationVersion
+ if (jacksonVersion.isNullOrEmpty() || jacksonVersion == COMPILED_JACKSON_VERSION) {
+ return BraintrustInvalidDataException(fallbackMessage, e)
+ }
+
+ return BraintrustException(
+ """
+ Jackson threw an unexpected exception and its runtime version ($jacksonVersion) mismatches the version the SDK was compiled with ($COMPILED_JACKSON_VERSION).
+
+ You may be using a version of `com.fasterxml.jackson.core:jackson-databind` that's not binary compatible with the SDK.
+
+ This can happen if you are either:
+ 1. Directly depending on a different Jackson version
+ 2. Depending on some library that depends on a different Jackson version, potentially transitively
+
+ Double-check that you are depending on a compatible Jackson version.
+ """
+ .trimIndent(),
+ e,
+ )
+}
+
+const val COMPILED_JACKSON_VERSION = "2.18.1"
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Params.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Params.kt
new file mode 100644
index 00000000..f98bf142
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Params.kt
@@ -0,0 +1,16 @@
+package com.braintrustdata.api.core
+
+import com.braintrustdata.api.core.http.Headers
+import com.braintrustdata.api.core.http.QueryParams
+
+/** An interface representing parameters passed to a service method. */
+interface Params {
+ /** The full set of headers in the parameters, including both fixed and additional headers. */
+ fun _headers(): Headers
+
+ /**
+ * The full set of query params in the parameters, including both fixed and additional query
+ * params.
+ */
+ fun _queryParams(): QueryParams
+}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PhantomReachable.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PhantomReachable.kt
new file mode 100644
index 00000000..91785b1a
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PhantomReachable.kt
@@ -0,0 +1,56 @@
+@file:JvmName("PhantomReachable")
+
+package com.braintrustdata.api.core
+
+import com.braintrustdata.api.errors.BraintrustException
+import java.lang.reflect.InvocationTargetException
+
+/**
+ * Closes [closeable] when [observed] becomes only phantom reachable.
+ *
+ * This is a wrapper around a Java 9+ [java.lang.ref.Cleaner], or a no-op in older Java versions.
+ */
+@JvmSynthetic
+internal fun closeWhenPhantomReachable(observed: Any, closeable: AutoCloseable) {
+ check(observed !== closeable) {
+ "`observed` cannot be the same object as `closeable` because it would never become phantom reachable"
+ }
+ closeWhenPhantomReachable(observed, closeable::close)
+}
+
+/**
+ * Calls [close] when [observed] becomes only phantom reachable.
+ *
+ * This is a wrapper around a Java 9+ [java.lang.ref.Cleaner], or a no-op in older Java versions.
+ */
+@JvmSynthetic
+internal fun closeWhenPhantomReachable(observed: Any, close: () -> Unit) {
+ closeWhenPhantomReachable?.let { it(observed, close) }
+}
+
+private val closeWhenPhantomReachable: ((Any, () -> Unit) -> Unit)? by lazy {
+ try {
+ val cleanerClass = Class.forName("java.lang.ref.Cleaner")
+ val cleanerCreate = cleanerClass.getMethod("create")
+ val cleanerRegister =
+ cleanerClass.getMethod("register", Any::class.java, Runnable::class.java)
+ val cleanerObject = cleanerCreate.invoke(null);
+
+ { observed, close ->
+ try {
+ cleanerRegister.invoke(cleanerObject, observed, Runnable { close() })
+ } catch (e: ReflectiveOperationException) {
+ if (e is InvocationTargetException) {
+ when (val cause = e.cause) {
+ is RuntimeException,
+ is Error -> throw cause
+ }
+ }
+ throw BraintrustException("Unexpected reflective invocation failure", e)
+ }
+ }
+ } catch (e: ReflectiveOperationException) {
+ // We're running Java 8, which has no Cleaner.
+ null
+ }
+}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PrepareRequest.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PrepareRequest.kt
new file mode 100644
index 00000000..e9aaadff
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/PrepareRequest.kt
@@ -0,0 +1,24 @@
+@file:JvmName("PrepareRequest")
+
+package com.braintrustdata.api.core
+
+import com.braintrustdata.api.core.http.HttpRequest
+import java.util.concurrent.CompletableFuture
+
+@JvmSynthetic
+internal fun HttpRequest.prepare(clientOptions: ClientOptions, params: Params): HttpRequest =
+ toBuilder()
+ .putAllQueryParams(clientOptions.queryParams)
+ .replaceAllQueryParams(params._queryParams())
+ .putAllHeaders(clientOptions.headers)
+ .replaceAllHeaders(params._headers())
+ .build()
+
+@JvmSynthetic
+internal fun HttpRequest.prepareAsync(
+ clientOptions: ClientOptions,
+ params: Params,
+): CompletableFuture =
+ // This async version exists to make it easier to add async specific preparation logic in the
+ // future.
+ CompletableFuture.completedFuture(prepare(clientOptions, params))
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Properties.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Properties.kt
index 1c59eee1..a0924603 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Properties.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Properties.kt
@@ -34,14 +34,9 @@ fun getOsName(): String {
}
}
-fun getOsVersion(): String {
- return System.getProperty("os.version", "unknown")
-}
+fun getOsVersion(): String = System.getProperty("os.version", "unknown")
-fun getPackageVersion(): String {
- return Properties::class.java.`package`.implementationVersion ?: "unknown"
-}
+fun getPackageVersion(): String =
+ Properties::class.java.`package`.implementationVersion ?: "unknown"
-fun getJavaVersion(): String {
- return System.getProperty("java.version", "unknown")
-}
+fun getJavaVersion(): String = System.getProperty("java.version", "unknown")
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/RequestOptions.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/RequestOptions.kt
index 7a8c8b26..fe7c8372 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/RequestOptions.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/RequestOptions.kt
@@ -2,17 +2,7 @@ package com.braintrustdata.api.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 {
@@ -20,21 +10,37 @@ private constructor(
@JvmStatic fun none() = NONE
+ @JvmSynthetic
+ internal fun from(clientOptions: ClientOptions): RequestOptions =
+ builder()
+ .responseValidation(clientOptions.responseValidation)
+ .timeout(clientOptions.timeout)
+ .build()
+
@JvmStatic fun builder() = Builder()
}
- class 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/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Timeout.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Timeout.kt
new file mode 100644
index 00000000..06ee42fa
--- /dev/null
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Timeout.kt
@@ -0,0 +1,167 @@
+// File generated from our OpenAPI spec by Stainless.
+
+package com.braintrustdata.api.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 /* spotless:off */ other is Timeout && connect == other.connect && read == other.read && write == other.write && request == other.request /* spotless:on */
+ }
+
+ override fun hashCode(): Int = /* spotless:off */ Objects.hash(connect, read, write, request) /* spotless:on */
+
+ override fun toString() =
+ "Timeout{connect=$connect, read=$read, write=$write, request=$request}"
+}
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Utils.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Utils.kt
index dd23bef7..6761ce6a 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Utils.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Utils.kt
@@ -3,59 +3,62 @@
package com.braintrustdata.api.core
import com.braintrustdata.api.errors.BraintrustInvalidDataException
-import com.google.common.collect.ImmutableListMultimap
-import com.google.common.collect.ListMultimap
-import com.google.common.collect.Multimaps
import java.util.Collections
+import java.util.SortedMap
@JvmSynthetic
-internal fun T?.getOrThrow(name: String): T {
- if (this == null) {
- throw BraintrustInvalidDataException("'${name}' is not present")
- }
+internal fun T?.getOrThrow(name: String): T =
+ this ?: throw BraintrustInvalidDataException("`${name}` is not present")
- return this
-}
+@JvmSynthetic
+internal fun List.toImmutable(): List =
+ if (isEmpty()) Collections.emptyList() else Collections.unmodifiableList(toList())
@JvmSynthetic
-internal fun List.toUnmodifiable(): List {
- if (isEmpty()) {
- return Collections.emptyList()
- }
+internal fun Map.toImmutable(): Map =
+ if (isEmpty()) immutableEmptyMap() else Collections.unmodifiableMap(toMap())
- return Collections.unmodifiableList(this)
-}
+@JvmSynthetic internal fun immutableEmptyMap(): Map = Collections.emptyMap()
@JvmSynthetic
-internal fun Map.toUnmodifiable(): Map {
- if (isEmpty()) {
- return Collections.emptyMap()
- }
-
- return Collections.unmodifiableMap(this)
-}
+internal fun , V> SortedMap.toImmutable(): SortedMap =
+ if (isEmpty()) Collections.emptySortedMap()
+ else Collections.unmodifiableSortedMap(toSortedMap(comparator()))
+/**
+ * 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 fun ListMultimap.toUnmodifiable(): ListMultimap {
- if (isEmpty()) {
- return ImmutableListMultimap.of()
- }
+internal infix fun Any?.contentEquals(other: Any?): Boolean =
+ arrayOf(this).contentDeepEquals(arrayOf(other))
- return Multimaps.unmodifiableListMultimap(this)
-}
+/**
+ * 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 ListMultimap.getRequiredHeader(header: String): String {
- val value =
- entries()
- .stream()
- .filter { entry -> entry.key.equals(header, ignoreCase = true) }
- .map { entry -> entry.value }
- .findFirst()
- if (!value.isPresent) {
- throw BraintrustInvalidDataException("Could not find $header header")
+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 value.get()
+ return string
}
internal interface Enum
diff --git a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Values.kt b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Values.kt
index 9ede4db6..c25a05d5 100755
--- a/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Values.kt
+++ b/braintrust-java-core/src/main/kotlin/com/braintrustdata/api/core/Values.kt
@@ -27,10 +27,9 @@ import com.fasterxml.jackson.databind.node.JsonNodeType.OBJECT
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 java.nio.charset.Charset
+import java.io.InputStream
import java.util.Objects
import java.util.Optional
-import org.apache.hc.core5.http.ContentType
@JsonDeserialize(using = JsonField.Deserializer::class)
sealed class JsonField {
@@ -59,36 +58,69 @@ sealed class JsonField {
fun asBoolean(): Optional =
when (this) {
is JsonBoolean -> Optional.of(value)
+ is KnownValue -> Optional.ofNullable(value as? Boolean)
else -> Optional.empty()
}
fun asNumber(): Optional =
when (this) {
is JsonNumber -> Optional.of(value)
+ is KnownValue -> Optional.ofNullable(value as? Number)
else -> Optional.empty()
}
fun asString(): Optional =
when (this) {
is JsonString -> Optional.of(value)
+ is KnownValue -> Optional.ofNullable(value as? String)
else -> Optional.empty()
}
fun asStringOrThrow(): String =
- when (this) {
- is JsonString -> value
- else -> throw BraintrustInvalidDataException("Value is not a string")
- }
+ asString().orElseThrow { BraintrustInvalidDataException("Value is not a string") }
fun asArray(): Optional> =
when (this) {
is JsonArray -> Optional.of(values)
+ is KnownValue ->
+ Optional.ofNullable(
+ (value as? List<*>)?.map {
+ try {
+ JsonValue.from(it)
+ } catch (e: IllegalArgumentException) {
+ // The known value is a list, but not all values are convertible to
+ // `JsonValue`.
+ return Optional.empty()
+ }
+ }
+ )
else -> Optional.empty()
}
fun asObject(): Optional