From c01c6ea5843d9da0864fbd522453c8423772eebe Mon Sep 17 00:00:00 2001 From: Mouaad Aallam Date: Sat, 5 Oct 2024 13:13:35 +0200 Subject: [PATCH 1/7] use kotlin autocloseable --- gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../kotlin/com.aallam.openai.client/Closeable.kt | 13 ------------- .../kotlin/com.aallam.openai.client/OpenAI.kt | 2 +- .../com.aallam.openai.client/internal/OpenAIApi.kt | 2 +- .../internal/http/HttpRequester.kt | 3 +-- .../kotlin/com/aallam/openai/client/Closeable.kt | 5 ----- .../kotlin/com/aallam/openai/client/Closeable.kt | 3 --- .../kotlin/com/aallam/openai/client/Closeable.kt | 5 ----- .../com.aallam.openai.client/Closeable.wasmJs.kt | 13 ------------- 10 files changed, 5 insertions(+), 45 deletions(-) delete mode 100644 openai-client/src/commonMain/kotlin/com.aallam.openai.client/Closeable.kt delete mode 100644 openai-client/src/jsMain/kotlin/com/aallam/openai/client/Closeable.kt delete mode 100644 openai-client/src/jvmMain/kotlin/com/aallam/openai/client/Closeable.kt delete mode 100644 openai-client/src/nativeMain/kotlin/com/aallam/openai/client/Closeable.kt delete mode 100644 openai-client/src/wasmJsMain/kotlin/com.aallam.openai.client/Closeable.wasmJs.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index da5ffa6d..ab1eadc7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ kotlin = "2.0.0" coroutines = "1.8.1" serialization = "1.6.3" -ktor = "3.0.0-beta-2" +ktor = "3.0.0-rc-2" okio = "3.9.0" logback = "1.4.8" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9f4197d5..df97d72b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/Closeable.kt b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/Closeable.kt deleted file mode 100644 index 101c8d4e..00000000 --- a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/Closeable.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.aallam.openai.client - -/** - * Defines a closeable resource. - * This will be replaced by [AutoCloseable] once it becomes stable. - */ -public expect interface Closeable { - - /** - * Closes underlying resources - */ - public fun close() -} diff --git a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/OpenAI.kt b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/OpenAI.kt index f38771fe..25f0971f 100644 --- a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/OpenAI.kt +++ b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/OpenAI.kt @@ -11,7 +11,7 @@ import kotlin.time.Duration.Companion.seconds * OpenAI API. */ public interface OpenAI : Completions, Files, Edits, Embeddings, Models, Moderations, FineTunes, Images, Chat, Audio, - FineTuning, Assistants, Threads, Runs, Messages, VectorStores, Closeable + FineTuning, Assistants, Threads, Runs, Messages, VectorStores, AutoCloseable /** * Creates an instance of [OpenAI]. diff --git a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/OpenAIApi.kt b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/OpenAIApi.kt index 68b7cc2e..4612c433 100644 --- a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/OpenAIApi.kt +++ b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/OpenAIApi.kt @@ -29,4 +29,4 @@ internal class OpenAIApi( Messages by MessagesApi(requester), VectorStores by VectorStoresApi(requester), Batch by BatchApi(requester), - Closeable by requester + AutoCloseable by requester diff --git a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/http/HttpRequester.kt b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/http/HttpRequester.kt index 2a00dd96..d29ecd3c 100644 --- a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/http/HttpRequester.kt +++ b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/http/HttpRequester.kt @@ -1,6 +1,5 @@ package com.aallam.openai.client.internal.http -import com.aallam.openai.client.Closeable import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.statement.* @@ -9,7 +8,7 @@ import io.ktor.util.reflect.* /** * Http request performer. */ -internal interface HttpRequester : Closeable { +internal interface HttpRequester : AutoCloseable { /** * Perform an HTTP request and get a result. diff --git a/openai-client/src/jsMain/kotlin/com/aallam/openai/client/Closeable.kt b/openai-client/src/jsMain/kotlin/com/aallam/openai/client/Closeable.kt deleted file mode 100644 index bd5e15f1..00000000 --- a/openai-client/src/jsMain/kotlin/com/aallam/openai/client/Closeable.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.aallam.openai.client - -public actual interface Closeable { - public actual fun close() -} diff --git a/openai-client/src/jvmMain/kotlin/com/aallam/openai/client/Closeable.kt b/openai-client/src/jvmMain/kotlin/com/aallam/openai/client/Closeable.kt deleted file mode 100644 index ebeb6f10..00000000 --- a/openai-client/src/jvmMain/kotlin/com/aallam/openai/client/Closeable.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.aallam.openai.client - -public actual typealias Closeable = java.lang.AutoCloseable diff --git a/openai-client/src/nativeMain/kotlin/com/aallam/openai/client/Closeable.kt b/openai-client/src/nativeMain/kotlin/com/aallam/openai/client/Closeable.kt deleted file mode 100644 index bd5e15f1..00000000 --- a/openai-client/src/nativeMain/kotlin/com/aallam/openai/client/Closeable.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.aallam.openai.client - -public actual interface Closeable { - public actual fun close() -} diff --git a/openai-client/src/wasmJsMain/kotlin/com.aallam.openai.client/Closeable.wasmJs.kt b/openai-client/src/wasmJsMain/kotlin/com.aallam.openai.client/Closeable.wasmJs.kt deleted file mode 100644 index 8479a04d..00000000 --- a/openai-client/src/wasmJsMain/kotlin/com.aallam.openai.client/Closeable.wasmJs.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.aallam.openai.client - -/** - * Defines a closeable resource. - * This will be replaced by [AutoCloseable] once it becomes stable. - */ -public actual interface Closeable { - /** - * Closes underlying resources - */ - public actual fun close() - -} \ No newline at end of file From 4eb896af6971844a0a22463c11f6917cc92fe9b6 Mon Sep 17 00:00:00 2001 From: Mouaad Aallam Date: Sat, 5 Oct 2024 13:41:55 +0200 Subject: [PATCH 2/7] fix api changes --- gradle.properties | 1 - gradle/libs.versions.toml | 4 +++- .../com.aallam.openai.client/internal/extension/Request.kt | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index fab481b9..ece49ea1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,6 @@ kotlin.code.style=official kotlin.mpp.stability.nowarn=true kotlin.mpp.commonizerLogLevel=info -kotlin.js.compiler=ir # Lib GROUP=com.aallam.openai diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ab1eadc7..abd27c30 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,10 @@ [versions] -kotlin = "2.0.0" +kotlin = "2.0.20" coroutines = "1.8.1" serialization = "1.6.3" ktor = "3.0.0-rc-2" okio = "3.9.0" +kotlinio = "0.5.4" logback = "1.4.8" [libraries] @@ -29,6 +30,7 @@ ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock", version.ref = ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" } ktor-client-darwin = { group = "io.ktor", name = "ktor-client-darwin", version.ref = "ktor" } # Okio +kotlinxio = { group = "org.jetbrains.kotlinx", name = "kotlinx-io", version.ref = "kotlinio" } okio = { group = "com.squareup.okio", name = "okio", version.ref = "okio" } okio-nodefilesystem = { group = "com.squareup.okio", name = "okio-nodefilesystem", version.ref = "okio" } okio-fakefilesystem = { group = "com.squareup.okio", name = "okio-fakefilesystem", version.ref = "okio" } diff --git a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/extension/Request.kt b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/extension/Request.kt index 8aabcc8d..77110554 100644 --- a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/extension/Request.kt +++ b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/extension/Request.kt @@ -32,7 +32,7 @@ internal fun FormBuilder.appendFileSource(key: String, fileSource: FileSource) { val buffer = ByteArray(8192) // 8 KiB var bytesRead: Int while (source.read(buffer).also { bytesRead = it } != -1) { - writeFully(src = buffer, offset = 0, length = bytesRead) + writeFully(buffer = buffer, offset = 0, length = bytesRead) } } From a5eabd4173348a19420193bdcc6ddd08bcd9e396 Mon Sep 17 00:00:00 2001 From: Mouaad Aallam Date: Sun, 6 Oct 2024 10:51:36 +0200 Subject: [PATCH 3/7] update io usage --- build-support/src/main/kotlin/Platforms.kt | 4 +++- gradle/libs.versions.toml | 11 ++++------- openai-client/build.gradle.kts | 5 ++--- .../internal/api/AudioApi.kt | 7 ++----- .../internal/extension/Request.kt | 13 ++++++------- .../com/aallam/openai/client/TestAudio.kt | 2 -- .../openai/client/TestChatCompletionChunk.kt | 7 ++++--- .../aallam/openai/client/internal/Resource.kt | 13 ++++++------- .../com/aallam/openai/client/internal/Source.kt | 7 ++++--- .../internal/FileSystem.kt | 8 -------- .../aallam/openai/client/TestChatVisionJVM.kt | 17 ++++++++++++----- .../aallam/openai/client/internal/FileSystem.kt | 5 ----- .../internal/FileSystem.kt | 5 ----- openai-core/build.gradle.kts | 2 +- .../com.aallam.openai.api/file/FileSource.kt | 14 ++++++++------ sample/js/build.gradle.kts | 1 - .../com/aallam/openai/sample/jvm/Whisper.kt | 7 +++---- .../com/aallam/openai/sample/jvm/images.kt | 7 +++---- 18 files changed, 58 insertions(+), 77 deletions(-) delete mode 100644 openai-client/src/jsTest/kotlin/com.aallam.openai.client/internal/FileSystem.kt delete mode 100644 openai-client/src/jvmTest/kotlin/com/aallam/openai/client/internal/FileSystem.kt delete mode 100644 openai-client/src/nativeTest/kotlin/com.aallam.openai.client/internal/FileSystem.kt diff --git a/build-support/src/main/kotlin/Platforms.kt b/build-support/src/main/kotlin/Platforms.kt index ee8e8080..57660c15 100644 --- a/build-support/src/main/kotlin/Platforms.kt +++ b/build-support/src/main/kotlin/Platforms.kt @@ -45,7 +45,9 @@ fun KotlinMultiplatformExtension.native() { @OptIn(ExperimentalWasmDsl::class) fun KotlinMultiplatformExtension.jsWasm() { - wasmJs() + wasmJs { + nodejs() + } } fun KotlinMultiplatformExtension.jsNode() { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index abd27c30..e3c00656 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,8 @@ [versions] kotlin = "2.0.20" coroutines = "1.8.1" -serialization = "1.6.3" +serialization = "1.7.3" ktor = "3.0.0-rc-2" -okio = "3.9.0" kotlinio = "0.5.4" logback = "1.4.8" @@ -29,11 +28,9 @@ ktor-client-jetty = { group = "io.ktor", name = "ktor-client-jetty", version.ref ktor-client-mock = { group = "io.ktor", name = "ktor-client-mock", version.ref = "ktor" } ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" } ktor-client-darwin = { group = "io.ktor", name = "ktor-client-darwin", version.ref = "ktor" } -# Okio -kotlinxio = { group = "org.jetbrains.kotlinx", name = "kotlinx-io", version.ref = "kotlinio" } -okio = { group = "com.squareup.okio", name = "okio", version.ref = "okio" } -okio-nodefilesystem = { group = "com.squareup.okio", name = "okio-nodefilesystem", version.ref = "okio" } -okio-fakefilesystem = { group = "com.squareup.okio", name = "okio-fakefilesystem", version.ref = "okio" } +# IO +kotlinx-io-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-io-core", version.ref = "kotlinio" } +kotlinx-io-bytestring = { group = "org.jetbrains.kotlinx", name = "kotlinx-io-bytestring", version.ref = "kotlinio" } # Logback logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" } # ulid diff --git a/openai-client/build.gradle.kts b/openai-client/build.gradle.kts index ae5ca0c8..48a3321e 100644 --- a/openai-client/build.gradle.kts +++ b/openai-client/build.gradle.kts @@ -32,7 +32,8 @@ kotlin { dependencies { api(projects.openaiCore) api(libs.coroutines.core) - api(libs.okio) + api(libs.kotlinx.io.core) + implementation(libs.kotlinx.io.bytestring) implementation(libs.serialization.json) api(libs.ktor.client.core) implementation(libs.ktor.client.logging) @@ -47,7 +48,6 @@ kotlin { implementation(kotlin("test-common")) implementation(kotlin("test-annotations-common")) implementation(libs.coroutines.test) - implementation(libs.okio.fakefilesystem) } } val jvmMain by getting @@ -61,7 +61,6 @@ kotlin { val jsMain by getting { dependencies { - implementation(libs.okio.nodefilesystem) } } val jsTest by getting { diff --git a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/api/AudioApi.kt b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/api/AudioApi.kt index 85dcb734..7246d3b7 100644 --- a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/api/AudioApi.kt +++ b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/api/AudioApi.kt @@ -19,11 +19,8 @@ internal class AudioApi(val requester: HttpRequester) : Audio { @BetaOpenAI override suspend fun transcription(request: TranscriptionRequest, requestOptions: RequestOptions?): Transcription { return when (request.responseFormat) { - AudioResponseFormat.Json, AudioResponseFormat.VerboseJson, null -> transcriptionAsJson( - request, - requestOptions - ) - + AudioResponseFormat.Json, AudioResponseFormat.VerboseJson, null -> + transcriptionAsJson(request, requestOptions) else -> transcriptionAsString(request, requestOptions) } } diff --git a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/extension/Request.kt b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/extension/Request.kt index 77110554..29aee15d 100644 --- a/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/extension/Request.kt +++ b/openai-client/src/commonMain/kotlin/com.aallam.openai.client/internal/extension/Request.kt @@ -6,10 +6,10 @@ import com.aallam.openai.client.internal.JsonLenient import io.ktor.client.request.* import io.ktor.client.request.forms.* import io.ktor.http.* -import io.ktor.utils.io.core.* +import io.ktor.utils.io.core.readAvailable +import io.ktor.utils.io.core.writeFully +import kotlinx.io.buffered import kotlinx.serialization.json.* -import okio.buffer -import okio.use /** * Adds `stream` parameter to the request. @@ -27,14 +27,13 @@ internal inline fun streamRequestOf(serializable: T): JsonElement { } internal fun FormBuilder.appendFileSource(key: String, fileSource: FileSource) { - append(key, fileSource.name, ContentType.Application.OctetStream) { - fileSource.source.buffer().use { source -> + append(key = key, filename = fileSource.name, contentType = ContentType.Application.OctetStream) { + fileSource.source.buffered().use { source -> val buffer = ByteArray(8192) // 8 KiB var bytesRead: Int - while (source.read(buffer).also { bytesRead = it } != -1) { + while (source.readAvailable(buffer).also { bytesRead = it } != 0) { writeFully(buffer = buffer, offset = 0, length = bytesRead) } - } } } diff --git a/openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestAudio.kt b/openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestAudio.kt index fcc8fc75..563ebb5c 100644 --- a/openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestAudio.kt +++ b/openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestAudio.kt @@ -5,8 +5,6 @@ import com.aallam.openai.api.file.FileSource import com.aallam.openai.api.model.ModelId import com.aallam.openai.client.internal.TestFileSystem import com.aallam.openai.client.internal.testFilePath -import okio.FileSystem -import okio.Path.Companion.toPath import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue diff --git a/openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestChatCompletionChunk.kt b/openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestChatCompletionChunk.kt index 90ff4d81..f11341fe 100644 --- a/openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestChatCompletionChunk.kt +++ b/openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestChatCompletionChunk.kt @@ -5,21 +5,22 @@ import com.aallam.openai.api.file.FileSource import com.aallam.openai.client.internal.JsonLenient import com.aallam.openai.client.internal.TestFileSystem import com.aallam.openai.client.internal.testFilePath +import kotlinx.io.buffered +import kotlinx.io.readByteArray import kotlin.test.Test -import okio.buffer class TestChatCompletionChunk { @Test fun testContentFilterDeserialization() { val json = FileSource(path = testFilePath("json/azureContentFilterChunk.json"), fileSystem = TestFileSystem) - val actualJson = json.source.buffer().readByteArray().decodeToString() + val actualJson = json.source.buffered().readByteArray().decodeToString() JsonLenient.decodeFromString(actualJson) } @Test fun testDeserialization() { val json = FileSource(path = testFilePath("json/chatChunk.json"), fileSystem = TestFileSystem) - val actualJson = json.source.buffer().readByteArray().decodeToString() + val actualJson = json.source.buffered().readByteArray().decodeToString() JsonLenient.decodeFromString(actualJson) } } diff --git a/openai-client/src/commonTest/kotlin/com/aallam/openai/client/internal/Resource.kt b/openai-client/src/commonTest/kotlin/com/aallam/openai/client/internal/Resource.kt index 0ad1e90d..43915caa 100644 --- a/openai-client/src/commonTest/kotlin/com/aallam/openai/client/internal/Resource.kt +++ b/openai-client/src/commonTest/kotlin/com/aallam/openai/client/internal/Resource.kt @@ -1,21 +1,20 @@ package com.aallam.openai.client.internal -import okio.FileSystem -import okio.Path -import okio.Path.Companion.toPath +import kotlinx.io.files.Path +import kotlinx.io.files.SystemFileSystem /** * File system to access test files. */ -internal expect val TestFileSystem: FileSystem +internal val TestFileSystem = SystemFileSystem /** * Get [Path] of a given [fileName] test file. */ -fun testFilePath(fileName: String): Path = libRoot / "openai-client/src/commonTest/resources" / fileName +fun testFilePath(fileName: String): Path = Path(libRoot, "openai-client/src/commonTest/resources", fileName) /** * Get the library lib root. */ -private val libRoot - get() = env("LIB_ROOT")?.toPath() ?: error("Can't find `LIB_ROOT` environment variable") +private val libRoot: String + get() = env("LIB_ROOT") ?: error("Can't find `LIB_ROOT` environment variable") diff --git a/openai-client/src/commonTest/kotlin/com/aallam/openai/client/internal/Source.kt b/openai-client/src/commonTest/kotlin/com/aallam/openai/client/internal/Source.kt index 403f0f2d..e702f6b3 100644 --- a/openai-client/src/commonTest/kotlin/com/aallam/openai/client/internal/Source.kt +++ b/openai-client/src/commonTest/kotlin/com/aallam/openai/client/internal/Source.kt @@ -1,10 +1,11 @@ package com.aallam.openai.client.internal -import okio.Buffer -import okio.Source +import kotlinx.io.Buffer +import kotlinx.io.Source +import kotlinx.io.writeString internal fun String.asSource(): Source { val buffer = Buffer() - buffer.writeUtf8(this) + buffer.writeString(this) return buffer } diff --git a/openai-client/src/jsTest/kotlin/com.aallam.openai.client/internal/FileSystem.kt b/openai-client/src/jsTest/kotlin/com.aallam.openai.client/internal/FileSystem.kt deleted file mode 100644 index 076da047..00000000 --- a/openai-client/src/jsTest/kotlin/com.aallam.openai.client/internal/FileSystem.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.aallam.openai.client.internal - -import okio.FileSystem -import okio.NodeJsFileSystem -import okio.Path.Companion.toPath -import okio.Source - -internal actual val TestFileSystem: FileSystem = NodeJsFileSystem diff --git a/openai-client/src/jvmTest/kotlin/com/aallam/openai/client/TestChatVisionJVM.kt b/openai-client/src/jvmTest/kotlin/com/aallam/openai/client/TestChatVisionJVM.kt index 0046e93b..12654fc3 100644 --- a/openai-client/src/jvmTest/kotlin/com/aallam/openai/client/TestChatVisionJVM.kt +++ b/openai-client/src/jvmTest/kotlin/com/aallam/openai/client/TestChatVisionJVM.kt @@ -2,18 +2,25 @@ package com.aallam.openai.client import com.aallam.openai.api.chat.* import com.aallam.openai.api.model.ModelId -import okio.FileSystem -import okio.Path.Companion.toPath +import kotlinx.io.buffered +import kotlinx.io.bytestring.encode +import kotlinx.io.files.Path +import kotlinx.io.files.SystemFileSystem +import kotlinx.io.readByteString +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi + import kotlin.test.* class TestChatVisionJVM : TestOpenAI() { + @OptIn(ExperimentalEncodingApi::class) @Test fun encoded() = test { - val byteString = FileSystem.RESOURCES.read("nature.jpeg".toPath()) { - readByteString() + val byteString = SystemFileSystem.source(path = Path("src/jvmTest/resources/nature.jpeg")).buffered().use { + it.readByteString() } - val encoded = byteString.base64() + val encoded = Base64.encode(source = byteString) val request = chatCompletionRequest { model = ModelId("gpt-4-vision-preview") messages { diff --git a/openai-client/src/jvmTest/kotlin/com/aallam/openai/client/internal/FileSystem.kt b/openai-client/src/jvmTest/kotlin/com/aallam/openai/client/internal/FileSystem.kt deleted file mode 100644 index 704f385b..00000000 --- a/openai-client/src/jvmTest/kotlin/com/aallam/openai/client/internal/FileSystem.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.aallam.openai.client.internal - -import okio.FileSystem - -internal actual val TestFileSystem: FileSystem = FileSystem.SYSTEM diff --git a/openai-client/src/nativeTest/kotlin/com.aallam.openai.client/internal/FileSystem.kt b/openai-client/src/nativeTest/kotlin/com.aallam.openai.client/internal/FileSystem.kt deleted file mode 100644 index 704f385b..00000000 --- a/openai-client/src/nativeTest/kotlin/com.aallam.openai.client/internal/FileSystem.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.aallam.openai.client.internal - -import okio.FileSystem - -internal actual val TestFileSystem: FileSystem = FileSystem.SYSTEM diff --git a/openai-core/build.gradle.kts b/openai-core/build.gradle.kts index 1b7c5be6..fa4eed71 100644 --- a/openai-core/build.gradle.kts +++ b/openai-core/build.gradle.kts @@ -23,7 +23,7 @@ kotlin { } val commonMain by getting { dependencies { - api(libs.okio) + api(libs.kotlinx.io.core) api(libs.serialization.json) implementation(libs.serialization.core) } diff --git a/openai-core/src/commonMain/kotlin/com.aallam.openai.api/file/FileSource.kt b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/file/FileSource.kt index e9e7cafe..2b6a6c66 100644 --- a/openai-core/src/commonMain/kotlin/com.aallam.openai.api/file/FileSource.kt +++ b/openai-core/src/commonMain/kotlin/com.aallam.openai.api/file/FileSource.kt @@ -1,9 +1,11 @@ package com.aallam.openai.api.file import com.aallam.openai.api.OpenAIDsl -import okio.FileSystem -import okio.Path -import okio.Source +import kotlinx.io.RawSource +import kotlinx.io.Source +import kotlinx.io.files.FileSystem +import kotlinx.io.files.Path +import kotlinx.io.files.SystemFileSystem /** * Represents a file resource. @@ -17,16 +19,16 @@ public class FileSource( /** * File source. */ - public val source: Source, + public val source: RawSource, ) { /** * Create [FileSource] instance. * - * @param path file path to upload + * @param path the file path to upload * @param fileSystem file system to be used */ - public constructor(path: Path, fileSystem: FileSystem) : this(path.name, fileSystem.source(path)) + public constructor(path: Path, fileSystem: FileSystem = SystemFileSystem) : this(path.name, fileSystem.source(path)) } /** diff --git a/sample/js/build.gradle.kts b/sample/js/build.gradle.kts index 5a14f1d0..39f0feb8 100644 --- a/sample/js/build.gradle.kts +++ b/sample/js/build.gradle.kts @@ -15,7 +15,6 @@ kotlin { dependencies { //implementation("com.aallam.openai:openai-client:") implementation(projects.openaiClient) - implementation(libs.okio.nodefilesystem) } } } diff --git a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Whisper.kt b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Whisper.kt index dc41696f..b6689817 100644 --- a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Whisper.kt +++ b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Whisper.kt @@ -5,13 +5,12 @@ import com.aallam.openai.api.audio.TranslationRequest import com.aallam.openai.api.file.FileSource import com.aallam.openai.api.model.ModelId import com.aallam.openai.client.OpenAI -import okio.FileSystem -import okio.Path.Companion.toPath +import kotlinx.io.files.Path suspend fun whisper(openAI: OpenAI) { println("\n>️ Create transcription...") val transcriptionRequest = TranscriptionRequest( - audio = FileSource(path = "micro-machines.wav".toPath(), fileSystem = FileSystem.RESOURCES), + audio = FileSource(path = Path("micro-machines.wav")), model = ModelId("whisper-1"), ) val transcription = openAI.transcription(transcriptionRequest) @@ -19,7 +18,7 @@ suspend fun whisper(openAI: OpenAI) { println("\n>️ Create translation...") val translationRequest = TranslationRequest( - audio = FileSource(path = "multilingual.wav".toPath(), fileSystem = FileSystem.RESOURCES), + audio = FileSource(path = Path("multilingual.wav")), model = ModelId("whisper-1"), ) val translation = openAI.translation(translationRequest) diff --git a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/images.kt b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/images.kt index 7035c852..ebd382ee 100644 --- a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/images.kt +++ b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/images.kt @@ -5,8 +5,7 @@ import com.aallam.openai.api.image.ImageCreation import com.aallam.openai.api.image.ImageEdit import com.aallam.openai.api.image.ImageSize import com.aallam.openai.client.OpenAI -import okio.FileSystem -import okio.Path.Companion.toPath +import kotlinx.io.files.Path suspend fun images(openAI: OpenAI) { println("\n> Create images...") @@ -21,8 +20,8 @@ suspend fun images(openAI: OpenAI) { println("\n> Edit images...") val imageEdit = ImageEdit( - image = FileSource(path = "image.png".toPath(), fileSystem = FileSystem.RESOURCES), - mask = FileSource(path = "image.png".toPath(), fileSystem = FileSystem.RESOURCES), + image = FileSource(path = Path("image.png")), + mask = FileSource(path = Path("image.png")), prompt = "a sunlit indoor lounge area with a pool containing a flamingo", n = 1, size = ImageSize.is1024x1024, From 144748b1d9a6653a6a27019b78007acd4cf5b822 Mon Sep 17 00:00:00 2001 From: Mouaad Aallam Date: Sun, 6 Oct 2024 19:29:18 +0200 Subject: [PATCH 4/7] updare readme and snapshot --- CHANGELOG.md | 12 ++++++++++++ gradle.properties | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e59cb956..3e2ea5d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## Unreleased + +### Added +- WasmJs target (#387) + +### Changed +- Upgrade to Kotlin 2.0 (#387) +- Update Ktor to 3.0 (#387) + +### Breaking Changes +- Replace okio by kotlinx.io (#387) + ## 3.8.2 ### Added diff --git a/gradle.properties b/gradle.properties index ece49ea1..7e80e979 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ kotlin.mpp.commonizerLogLevel=info # Lib GROUP=com.aallam.openai -VERSION_NAME=3.9.0 +VERSION_NAME=4.0.0-SNAPSHOT # OSS SONATYPE_HOST=DEFAULT From 07d4d9d1d72cac15920ad74854fe776a48247a0a Mon Sep 17 00:00:00 2001 From: Mouaad Aallam Date: Sun, 6 Oct 2024 20:12:26 +0200 Subject: [PATCH 5/7] fix wasmjs tests --- .../kotlin/com.aallam.openai.client/internal/Env.kt | 7 +++++++ .../com/aallam/openai/sample/jvm/AssistantsRetrieval.kt | 6 ++---- 2 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 openai-client/src/wasmJsTest/kotlin/com.aallam.openai.client/internal/Env.kt diff --git a/openai-client/src/wasmJsTest/kotlin/com.aallam.openai.client/internal/Env.kt b/openai-client/src/wasmJsTest/kotlin/com.aallam.openai.client/internal/Env.kt new file mode 100644 index 00000000..c1bf3036 --- /dev/null +++ b/openai-client/src/wasmJsTest/kotlin/com.aallam.openai.client/internal/Env.kt @@ -0,0 +1,7 @@ +package com.aallam.openai.client.internal + +internal actual fun env(name: String): String? { + return getEnv(name) +} + +fun getEnv(value: String): String? = js("""globalThis.process.env[value]""") \ No newline at end of file diff --git a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/AssistantsRetrieval.kt b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/AssistantsRetrieval.kt index 57c80468..94f33fec 100644 --- a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/AssistantsRetrieval.kt +++ b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/AssistantsRetrieval.kt @@ -14,14 +14,13 @@ import com.aallam.openai.api.model.ModelId import com.aallam.openai.api.run.RunRequest import com.aallam.openai.client.OpenAI import kotlinx.coroutines.delay -import okio.FileSystem -import okio.Path.Companion.toPath +import kotlinx.io.files.Path @OptIn(BetaOpenAI::class) suspend fun assistantsRetrieval(openAI: OpenAI) { // 1. Upload a file with an "assistants" purpose - val fileUpload = FileUpload(file = FileSource("udhr.pdf".toPath(), FileSystem.RESOURCES), purpose = Purpose("assistants")) + val fileUpload = FileUpload(file = FileSource(Path("udhr.pdf")), purpose = Purpose("assistants")) val knowledgeBase = openAI.file(request = fileUpload) val assistant = openAI.assistant( @@ -30,7 +29,6 @@ suspend fun assistantsRetrieval(openAI: OpenAI) { instructions = "You are a chatbot specialized in 'The Universal Declaration of Human Rights.' Answer questions and provide information based on this document.", tools = listOf(AssistantTool.FileSearch), model = ModelId("gpt-4-1106-preview"), - fileIds = listOf(knowledgeBase.id) ) ) From f47625dce8951bcdafaa9126a92357462fc5b78f Mon Sep 17 00:00:00 2001 From: Mouaad Aallam Date: Sun, 6 Oct 2024 20:31:24 +0200 Subject: [PATCH 6/7] fix sample js --- sample/README.md | 2 +- sample/js/src/{main => jsMain}/kotlin/main.kt | 33 +++++------------- .../src/{main => jsMain}/resources/image.png | Bin .../src/{main => jsMain}/resources/mask.png | Bin .../resources/micro-machines.wav | Bin .../resources/multilingual.wav | Bin 6 files changed, 9 insertions(+), 26 deletions(-) rename sample/js/src/{main => jsMain}/kotlin/main.kt (75%) rename sample/js/src/{main => jsMain}/resources/image.png (100%) rename sample/js/src/{main => jsMain}/resources/mask.png (100%) rename sample/js/src/{main => jsMain}/resources/micro-machines.wav (100%) rename sample/js/src/{main => jsMain}/resources/multilingual.wav (100%) diff --git a/sample/README.md b/sample/README.md index 3ad191cb..0f07f81e 100644 --- a/sample/README.md +++ b/sample/README.md @@ -7,7 +7,7 @@ JVM, JS and Native sample apps. | Target | command | |--------|-------------------------------------------------------| | jvm | `./gradlew :sample:jvm:run` | -| js | `./gradlew :sample:js:run` | +| js | `./gradlew :sample:js:jsNodeDevelopmentRun` | | native | `./gradlew :sample:native:runReleaseExecutableNative` | _PS: environment variable `OPENAI_API_KEY` must be set._ diff --git a/sample/js/src/main/kotlin/main.kt b/sample/js/src/jsMain/kotlin/main.kt similarity index 75% rename from sample/js/src/main/kotlin/main.kt rename to sample/js/src/jsMain/kotlin/main.kt index 34e762e6..916772f8 100644 --- a/sample/js/src/main/kotlin/main.kt +++ b/sample/js/src/jsMain/kotlin/main.kt @@ -12,45 +12,28 @@ import com.aallam.openai.api.image.ImageSize import com.aallam.openai.api.logging.LogLevel import com.aallam.openai.api.model.ModelId import com.aallam.openai.api.moderation.ModerationRequest -import com.aallam.openai.api.LegacyOpenAI import com.aallam.openai.client.LoggingConfig import com.aallam.openai.client.OpenAI import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach -import okio.NodeJsFileSystem -import okio.Path.Companion.toPath +import kotlinx.io.files.Path import kotlin.coroutines.coroutineContext -@OptIn(LegacyOpenAI::class) suspend fun main() { val apiKey = js("process.env.OPENAI_API_KEY").unsafeCast() val token = requireNotNull(apiKey) { "OPENAI_API_KEY environment variable must be set." } - val openAI = OpenAI(token = token, logging = LoggingConfig(LogLevel.All)) + val openAI = OpenAI(token = token, logging = LoggingConfig(LogLevel.Info)) val scope = CoroutineScope(coroutineContext) println("> Getting available models...") openAI.models().forEach(::println) - println("\n> Getting ada model...") - val ada = openAI.model(modelId = ModelId("text-ada-001")) + println("\n> Getting model...") + val ada = openAI.model(modelId = ModelId("gpt-3.5-turbo")) println(ada) - println("\n>️ Creating completion...") - val completionRequest = CompletionRequest( - model = ada.id, - prompt = "Somebody once told me the world is gonna roll me" - ) - openAI.completion(completionRequest).choices.forEach(::println) - - println("\n>️ Creating completion stream...") - openAI.completions(completionRequest) - .onEach { print(it.choices[0].text) } - .onCompletion { println() } - .launchIn(scope) - .join() - println("\n> Read files...") val files = openAI.files() println(files) @@ -75,8 +58,8 @@ suspend fun main() { println("\n> Edit images...") val imageEdit = ImageEdit( - image = FileSource(path = "kotlin/image.png".toPath(), fileSystem = NodeJsFileSystem), - mask = FileSource(path = "kotlin/mask.png".toPath(), fileSystem = NodeJsFileSystem), + image = FileSource(path = Path("kotlin","image.png")), + mask = FileSource(path = Path("kotlin","mask.png")), prompt = "a sunlit indoor lounge area with a pool containing a flamingo", n = 1, size = ImageSize.is1024x1024, @@ -109,7 +92,7 @@ suspend fun main() { println("\n>️ Create transcription...") val transcriptionRequest = TranscriptionRequest( - audio = FileSource(path = "kotlin/micro-machines.wav".toPath(), fileSystem = NodeJsFileSystem), + audio = FileSource(path = Path("kotlin","micro-machines.wav")), model = ModelId("whisper-1"), ) val transcription = openAI.transcription(transcriptionRequest) @@ -117,7 +100,7 @@ suspend fun main() { println("\n>️ Create translation...") val translationRequest = TranslationRequest( - audio = FileSource(path = "kotlin/multilingual.wav".toPath(), fileSystem = NodeJsFileSystem), + audio = FileSource(path = Path("kotlin", "multilingual.wav")), model = ModelId("whisper-1"), ) val translation = openAI.translation(translationRequest) diff --git a/sample/js/src/main/resources/image.png b/sample/js/src/jsMain/resources/image.png similarity index 100% rename from sample/js/src/main/resources/image.png rename to sample/js/src/jsMain/resources/image.png diff --git a/sample/js/src/main/resources/mask.png b/sample/js/src/jsMain/resources/mask.png similarity index 100% rename from sample/js/src/main/resources/mask.png rename to sample/js/src/jsMain/resources/mask.png diff --git a/sample/js/src/main/resources/micro-machines.wav b/sample/js/src/jsMain/resources/micro-machines.wav similarity index 100% rename from sample/js/src/main/resources/micro-machines.wav rename to sample/js/src/jsMain/resources/micro-machines.wav diff --git a/sample/js/src/main/resources/multilingual.wav b/sample/js/src/jsMain/resources/multilingual.wav similarity index 100% rename from sample/js/src/main/resources/multilingual.wav rename to sample/js/src/jsMain/resources/multilingual.wav From 52b6848203025cd3ee84fcb8b5859d89cc61793f Mon Sep 17 00:00:00 2001 From: Mouaad Aallam Date: Sun, 6 Oct 2024 20:54:05 +0200 Subject: [PATCH 7/7] update jvm sample --- .../com/aallam/openai/sample/jvm/Resources.kt | 14 ++++++++++++++ .../kotlin/com/aallam/openai/sample/jvm/Whisper.kt | 5 ++--- .../kotlin/com/aallam/openai/sample/jvm/images.kt | 5 ++--- sample/native/src/nativeMain/kotlin/main.kt | 11 +++++------ 4 files changed, 23 insertions(+), 12 deletions(-) create mode 100644 sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Resources.kt diff --git a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Resources.kt b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Resources.kt new file mode 100644 index 00000000..e76d33a2 --- /dev/null +++ b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Resources.kt @@ -0,0 +1,14 @@ +package com.aallam.openai.sample.jvm + +import kotlinx.io.files.Path + +object Resources { + fun path(resource: String): Path { + return Path(path = getPath(resource)) + } + + private fun getPath(resource: String): String { + return Resources::class.java.getResource("/$resource")?.path + ?: throw IllegalStateException("Resource $resource not found") + } +} diff --git a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Whisper.kt b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Whisper.kt index b6689817..3f8e6f55 100644 --- a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Whisper.kt +++ b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/Whisper.kt @@ -5,12 +5,11 @@ import com.aallam.openai.api.audio.TranslationRequest import com.aallam.openai.api.file.FileSource import com.aallam.openai.api.model.ModelId import com.aallam.openai.client.OpenAI -import kotlinx.io.files.Path suspend fun whisper(openAI: OpenAI) { println("\n>️ Create transcription...") val transcriptionRequest = TranscriptionRequest( - audio = FileSource(path = Path("micro-machines.wav")), + audio = FileSource(path = Resources.path("micro-machines.wav")), model = ModelId("whisper-1"), ) val transcription = openAI.transcription(transcriptionRequest) @@ -18,7 +17,7 @@ suspend fun whisper(openAI: OpenAI) { println("\n>️ Create translation...") val translationRequest = TranslationRequest( - audio = FileSource(path = Path("multilingual.wav")), + audio = FileSource(path = Resources.path("multilingual.wav")), model = ModelId("whisper-1"), ) val translation = openAI.translation(translationRequest) diff --git a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/images.kt b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/images.kt index ebd382ee..f00284ee 100644 --- a/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/images.kt +++ b/sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/images.kt @@ -5,7 +5,6 @@ import com.aallam.openai.api.image.ImageCreation import com.aallam.openai.api.image.ImageEdit import com.aallam.openai.api.image.ImageSize import com.aallam.openai.client.OpenAI -import kotlinx.io.files.Path suspend fun images(openAI: OpenAI) { println("\n> Create images...") @@ -20,8 +19,8 @@ suspend fun images(openAI: OpenAI) { println("\n> Edit images...") val imageEdit = ImageEdit( - image = FileSource(path = Path("image.png")), - mask = FileSource(path = Path("image.png")), + image = FileSource(path = Resources.path("image.png")), + mask = FileSource(path = Resources.path("image.png")), prompt = "a sunlit indoor lounge area with a pool containing a flamingo", n = 1, size = ImageSize.is1024x1024, diff --git a/sample/native/src/nativeMain/kotlin/main.kt b/sample/native/src/nativeMain/kotlin/main.kt index 38bea70c..d5cd61ec 100644 --- a/sample/native/src/nativeMain/kotlin/main.kt +++ b/sample/native/src/nativeMain/kotlin/main.kt @@ -18,8 +18,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking -import okio.FileSystem -import okio.Path.Companion.toPath +import kotlinx.io.files.Path import platform.posix.getenv fun main(): Unit = runBlocking { @@ -62,8 +61,8 @@ fun main(): Unit = runBlocking { println("\n> Edit images...") val imageEdit = ImageEdit( - image = FileSource(path = "$resourcesPrefix/image.png".toPath(), fileSystem = FileSystem.SYSTEM), - mask = FileSource(path = "$resourcesPrefix/mask.png".toPath(), fileSystem = FileSystem.SYSTEM), + image = FileSource(path = Path(resourcesPrefix, "image.png")), + mask = FileSource(path = Path(resourcesPrefix, "mask.png")), prompt = "a sunlit indoor lounge area with a pool containing a flamingo", n = 1, size = ImageSize.is1024x1024, @@ -96,7 +95,7 @@ fun main(): Unit = runBlocking { println("\n>️ Create transcription...") val transcriptionRequest = TranscriptionRequest( - audio = FileSource(path = "$resourcesPrefix/micro-machines.wav".toPath(), fileSystem = FileSystem.SYSTEM), + audio = FileSource(path = Path(resourcesPrefix, "micro-machines.wav")), model = ModelId("whisper-1"), ) val transcription = openAI.transcription(transcriptionRequest) @@ -104,7 +103,7 @@ fun main(): Unit = runBlocking { println("\n>️ Create translation...") val translationRequest = TranslationRequest( - audio = FileSource(path = "$resourcesPrefix/multilingual.wav".toPath(), fileSystem = FileSystem.SYSTEM), + audio = FileSource(path = Path(resourcesPrefix, "multilingual.wav")), model = ModelId("whisper-1"), ) val translation = openAI.translation(translationRequest)