diff --git a/.gitignore b/.gitignore index 8b0a0ba..c52b98f 100644 --- a/.gitignore +++ b/.gitignore @@ -42,5 +42,3 @@ bin/ .DS_Store /*.hprof - -/kotlin-js-store diff --git a/README.md b/README.md index 64d0746..4fb2af0 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Unofficial Kotlin multiplatform variant of the [Antropic SDK](https://docs.anthropic.com/en/api/client-sdks). -[Maven Central Version](https://central.sonatype.com/namespace/com.xemantic.anthropic) +[Maven Central Version](https://central.sonatype.com/namespace/com.xemantic.anthropic/anthropic-sdk-kotlin) [GitHub Release Date](https://github.com/xemantic/anthropic-sdk-kotlin/releases) [license](https://github.com/xemantic/anthropic-sdk-kotlin/blob/main/LICENSE) diff --git a/build.gradle.kts b/build.gradle.kts index de9afc6..879593c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,13 +1,12 @@ -@file:OptIn(ExperimentalKotlinGradlePluginApi::class) +@file:OptIn(ExperimentalKotlinGradlePluginApi::class, ExperimentalWasmDsl::class) import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestLogEvent import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinVersion -import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest -import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest plugins { alias(libs.plugins.kotlin.multiplatform) @@ -40,10 +39,12 @@ val sonatypePassword: String? by project val skipTests = isReleaseBuild println(""" - Project: ${project.name} - Version: ${project.version} - Release: $isReleaseBuild -""".trimIndent() ++-------------------------------------------- +| Project: ${project.name} +| Version: ${project.version} +| Release build: $isReleaseBuild ++-------------------------------------------- +""" ) repositories { @@ -52,11 +53,15 @@ repositories { kotlin { - //explicitApi() // check with serialization? + compilerOptions { + apiVersion = kotlinTarget + languageVersion = kotlinTarget + extraWarnings.set(true) + freeCompilerArgs.add("-Xmulti-dollar-interpolation") + progressiveMode = true + } + jvm { - testRuns["test"].executionTask.configure { - useJUnitPlatform() - } // set up according to https://jakewharton.com/gradle-toolchains-are-rarely-a-good-idea/ compilerOptions { apiVersion = kotlinTarget @@ -68,27 +73,68 @@ kotlin { } if (!isJvmOnlyBuild) { - js { - browser() - nodejs() + // browser tests switched off for a moment + browser { + testTask { + // for unknown reason browser tests are failing + enabled = false + useKarma { + useChromeHeadless() + } + } + } + nodejs { + testTask { + useMocha { + timeout = "20s" + } + } + } binaries.library() } -// linuxX64() -// -// mingwX64() -// macosArm64() - -// val hostOs = System.getProperty("os.name") -// val isMingwX64 = hostOs.startsWith("Windows") -// val nativeTarget = when { -// hostOs == "Mac OS X" -> macosX64("native") -// hostOs == "Linux" -> linuxX64("native") -// isMingwX64 -> mingwX64("native") -// else -> throw GradleException("Host OS is not supported in Kotlin/Native.") +// wasmJs { +// browser() +// nodejs() +// //d8() +// binaries.library() // } +// wasmWasi { +// nodejs() +// binaries.library() +// } + + // native, see https://kotlinlang.org/docs/native-target-support.html + // tier 1 + macosX64() + macosArm64() + iosSimulatorArm64() + iosX64() + iosArm64() + + // tier 2 + linuxX64() + linuxArm64() + watchosSimulatorArm64() + watchosX64() + watchosArm32() + watchosArm64() + tvosSimulatorArm64() + tvosX64() + tvosArm64() + +// // tier 3 +// androidNativeArm32() +// androidNativeArm64() +// androidNativeX86() +// androidNativeX64() + mingwX64() +// watchosDeviceArm64() + +// @OptIn(ExperimentalSwiftExportDsl::class) +// swiftExport {} } sourceSets { @@ -100,6 +146,7 @@ kotlin { implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.client.logging) implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.xemantic.ai.tool.schema) } } @@ -146,6 +193,20 @@ kotlin { } +if (!isJvmOnlyBuild) { +//// skip test for certain targets which are not fully supported by kotest +////tasks.named("compileTestKotlinWasmWasi") { enabled = false} + tasks.named("iosSimulatorArm64Test") { enabled = false } + tasks.named("watchosSimulatorArm64Test") { enabled = false } + tasks.named("tvosSimulatorArm64Test") { enabled = false } +//tasks.named("androidNativeArm64Test") { enabled = false } +//tasks.named("androidNativeX64Test") { enabled = false } +//tasks.named("androidNativeX86Test") { enabled = false } +//tasks.named("compileTestKotlinAndroidNativeX64") { enabled = false } +// +//// skip tests which require XCode components to be installed +} + fun isNonStable(version: String): Boolean { val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { version.uppercase().contains(it) } val regex = "^[0-9,.v-]+(-r)?$".toRegex() @@ -171,31 +232,16 @@ tasks.withType { } } - - -if (!isJvmOnlyBuild) { - - tasks.withType { - enabled = !skipTests - } - - tasks.withType { - // for now always skip JS tests, until we will find how to safely pass apiKey to them - enabled = false - } - -} - powerAssert { - functions = listOf( - "io.kotest.matchers.shouldBe" - ) - includedSourceSets = listOf("commonTest", "jvmTest", "nativeTest") +// functions = listOf( +// "io.kotest.matchers.shouldBe" +// ) +// includedSourceSets = listOf("commonTest", "jvmTest", "nativeTest") } // maybe this one is not necessary? tasks.dokkaHtml.configure { - outputDirectory.set(buildDir.resolve("dokka")) + outputDirectory.set(layout.buildDirectory.dir("dokka")) } val javadocJar by tasks.registering(Jar::class) { @@ -219,9 +265,6 @@ publishing { publications { withType { artifact(javadocJar) -// from(components["kotlin"]) -// artifact(javadocJar) -// artifact(sourcesJar) pom { name = "anthropic-sdk-kotlin" description = "Kotlin multiplatform client for accessing Ahtropic APIs" diff --git a/gradle.properties b/gradle.properties index 69bd0b8..4fe38ea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,6 @@ kotlin.code.style=official kotlin.js.generate.executable.default=false kotlin.native.ignoreDisabledTargets=true +kotlin.daemon.jvmargs=-Xmx1000m -Xms500m group=com.xemantic.anthropic version=0.10-SNAPSHOT diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5caa968..639822e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlinTarget = "2.0" +kotlinTarget = "2.1" javaTarget = "17" kotlin = "2.1.0" @@ -8,6 +8,8 @@ kotlinxDatetime = "0.6.1" ktor = "3.0.1" kotest = "6.0.0.M1" +xemanticAiToolSchema = "0.1.1" + # logging is not used at the moment, might be enabled later log4j = "2.24.2" jackson = "2.18.2" @@ -21,6 +23,9 @@ kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotl kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } +# xemantic +xemantic-ai-tool-schema = { module = "com.xemantic.ai:xemantic-ai-tool-schema", version.ref = "xemanticAiToolSchema"} + # logging libs log4j-slf4j2 = { module = "org.apache.logging.log4j:log4j-slf4j2-impl", version.ref = "log4j" } log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } diff --git a/karma.config.d/karma-config.js b/karma.config.d/karma-config.js new file mode 100644 index 0000000..9cb5089 --- /dev/null +++ b/karma.config.d/karma-config.js @@ -0,0 +1,7 @@ +config.set({ + client: { + mocha: { + timeout: 20000 + } + } +}); diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock new file mode 100644 index 0000000..f0989a4 --- /dev/null +++ b/kotlin-js-store/yarn.lock @@ -0,0 +1,529 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@js-joda/core@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" + integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== + +ansi-colors@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browser-stdout@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +camelcase@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +debug@^4.3.5: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + +diff@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" + integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +format-util@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" + integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +kotlin-web-helpers@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/kotlin-web-helpers/-/kotlin-web-helpers-2.0.0.tgz#b112096b273c1e733e0b86560998235c09a19286" + integrity sha512-xkVGl60Ygn/zuLkDPx+oHj7jeLR7hCvoNF99nhwXMn8a3ApB4lLiC9pk4ol4NHPjyoCbvQctBqvzUcp8pkqyWw== + dependencies: + format-util "^1.0.5" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +minimatch@^5.0.1, minimatch@^5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +mocha@10.7.3: + version "10.7.3" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.7.3.tgz#ae32003cabbd52b59aece17846056a68eb4b0752" + integrity sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A== + dependencies: + ansi-colors "^4.1.3" + browser-stdout "^1.3.1" + chokidar "^3.5.3" + debug "^4.3.5" + diff "^5.2.0" + escape-string-regexp "^4.0.0" + find-up "^5.0.0" + glob "^8.1.0" + he "^1.2.0" + js-yaml "^4.1.0" + log-symbols "^4.1.0" + minimatch "^5.1.6" + ms "^2.1.3" + serialize-javascript "^6.0.2" + strip-json-comments "^3.1.1" + supports-color "^8.1.1" + workerpool "^6.5.1" + yargs "^16.2.0" + yargs-parser "^20.2.9" + yargs-unparser "^2.0.0" + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +serialize-javascript@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + +source-map-support@0.5.21: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +typescript@5.5.4: + version "5.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" + integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== + +workerpool@^6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" + integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" + integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@^20.2.2, yargs-parser@^20.2.9: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-unparser@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/src/commonMain/kotlin/AnthropicJson.kt b/src/commonMain/kotlin/AnthropicJson.kt index 53fa254..e32bc73 100644 --- a/src/commonMain/kotlin/AnthropicJson.kt +++ b/src/commonMain/kotlin/AnthropicJson.kt @@ -35,6 +35,7 @@ import kotlinx.serialization.modules.polymorphic import kotlinx.serialization.modules.subclass // Note: surprisingly the order is important. This definition needs to go first. +@Suppress("UNUSED_ANONYMOUS_PARAMETER") private val anthropicSerializersModule = SerializersModule { polymorphicDefaultDeserializer(Response::class) { ResponseSerializer } polymorphicDefaultDeserializer(Content::class) { ContentSerializer } @@ -106,8 +107,10 @@ private object ContentSerializer : JsonContentPolymorphicSerializer( private object ToolSerializer : KSerializer { @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class) - override val descriptor: SerialDescriptor = - buildSerialDescriptor("ToolSerializer", SerialKind.CONTEXTUAL) + override val descriptor: SerialDescriptor = buildSerialDescriptor( + serialName = "com.xemantic.anthropic.Tool", + kind = SerialKind.CONTEXTUAL + ) override fun serialize(encoder: Encoder, value: Tool) { val serializer = when (value) { diff --git a/src/commonMain/kotlin/schema/JsonSchema.kt b/src/commonMain/kotlin/schema/JsonSchema.kt deleted file mode 100644 index 8e3326f..0000000 --- a/src/commonMain/kotlin/schema/JsonSchema.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.xemantic.anthropic.schema - -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.MetaSerializable -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@OptIn(ExperimentalSerializationApi::class) -@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) -@MetaSerializable -annotation class Description( - val value: String -) - -@Serializable -data class JsonSchema( - val type: String = "object", - val definitions: Map? = null, - val properties: Map? = null, - val required: List? = null, - @SerialName("\$ref") - var ref: String? = null -) - -@Serializable -data class JsonSchemaProperty( - val type: String? = null, - val description: String? = null, - val items: JsonSchemaProperty? = null, - val enum: List? = null, - @SerialName("\$ref") - val ref: String? = null -) diff --git a/src/commonMain/kotlin/schema/JsonSchemaGenerator.kt b/src/commonMain/kotlin/schema/JsonSchemaGenerator.kt deleted file mode 100644 index 64a16bd..0000000 --- a/src/commonMain/kotlin/schema/JsonSchemaGenerator.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.xemantic.anthropic.schema - -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlin.collections.set - -inline fun jsonSchemaOf(): JsonSchema = generateSchema( - serializer().descriptor -) - -@OptIn(ExperimentalSerializationApi::class) -fun generateSchema(descriptor: SerialDescriptor): JsonSchema { - val properties = mutableMapOf() - val required = mutableListOf() - val definitions = mutableMapOf() - - for (i in 0 until descriptor.elementsCount) { - val name = descriptor.getElementName(i) - val elementDescriptor = descriptor.getElementDescriptor(i) - val elementAnnotations = descriptor.getElementAnnotations(i) - val property = generateSchemaProperty( - elementDescriptor, - description = elementAnnotations - .filterIsInstance() - .firstOrNull() - ?.value, - definitions - ) - properties[name] = property - if (!descriptor.isElementOptional(i)) { - required.add(name) - } - } - - return JsonSchema( - type = "object", - properties = properties, - required = required, - definitions = if (definitions.isNotEmpty()) definitions else null - ) -} - -@OptIn(ExperimentalSerializationApi::class) -private fun generateSchemaProperty( - descriptor: SerialDescriptor, - description: String?, - definitions: MutableMap -): JsonSchemaProperty { - return when (descriptor.kind) { - PrimitiveKind.STRING -> JsonSchemaProperty("string", description) - PrimitiveKind.INT, PrimitiveKind.LONG -> JsonSchemaProperty("integer", description) - PrimitiveKind.FLOAT, PrimitiveKind.DOUBLE -> JsonSchemaProperty("number", description) - PrimitiveKind.BOOLEAN -> JsonSchemaProperty("boolean", description) - SerialKind.ENUM -> enumProperty(descriptor, description) - StructureKind.LIST -> JsonSchemaProperty( - type = "array", - items = generateSchemaProperty( - descriptor.getElementDescriptor(0), - description, - definitions - ) - ) - StructureKind.MAP -> JsonSchemaProperty("object", description) - StructureKind.CLASS -> { - // dots are not allowed in JSON Schema name, if the @SerialName was not - // specified, then fully qualified class name will be used, and we need - // to translate it - val refName = descriptor.serialName.replace('.', '_').trimEnd('?') - definitions[refName] = generateSchema(descriptor) - JsonSchemaProperty( - ref = "#/definitions/$refName", - description = description - ) - } - else -> JsonSchemaProperty("object", description) // Default case - } -} - -private fun enumProperty( - descriptor: SerialDescriptor, - description: String? -) = JsonSchemaProperty( - type = "string", - enum = descriptor.elementNames(), - description = description, -) - -@OptIn(ExperimentalSerializationApi::class) -private fun SerialDescriptor.elementNames(): List = buildList { - for (i in 0 until elementsCount) { - val name = getElementName(i) - add(name) - } -} diff --git a/src/commonMain/kotlin/tool/Tools.kt b/src/commonMain/kotlin/tool/Tools.kt index c271b65..ab4687d 100644 --- a/src/commonMain/kotlin/tool/Tools.kt +++ b/src/commonMain/kotlin/tool/Tools.kt @@ -1,10 +1,10 @@ package com.xemantic.anthropic.tool +import com.xemantic.ai.tool.schema.JsonSchema +import com.xemantic.ai.tool.schema.generator.jsonSchemaOf +import com.xemantic.ai.tool.schema.meta.Description import com.xemantic.anthropic.cache.CacheControl import com.xemantic.anthropic.content.Content -import com.xemantic.anthropic.schema.Description -import com.xemantic.anthropic.schema.JsonSchema -import com.xemantic.anthropic.schema.jsonSchemaOf import com.xemantic.anthropic.content.ToolResult import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer @@ -151,7 +151,7 @@ inline fun Tool( return DefaultTool( name = toolName, description = description, - inputSchema = jsonSchemaOf(), + inputSchema = jsonSchemaOf(suppressDescription = true), cacheControl = cacheControl ).apply { @Suppress("UNCHECKED_CAST") diff --git a/src/commonTest/kotlin/AnthropicTest.kt b/src/commonTest/kotlin/AnthropicTest.kt index 1fe639a..94a3bd8 100644 --- a/src/commonTest/kotlin/AnthropicTest.kt +++ b/src/commonTest/kotlin/AnthropicTest.kt @@ -12,7 +12,7 @@ import com.xemantic.anthropic.tool.FibonacciTool import com.xemantic.anthropic.tool.TestDatabase import com.xemantic.anthropic.content.Text import com.xemantic.anthropic.content.ToolUse -import io.kotest.assertions.assertSoftly +import com.xemantic.anthropic.test.assert import io.kotest.matchers.ints.shouldBeGreaterThan import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe @@ -23,12 +23,13 @@ import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.runTest +import kotlin.test.Ignore import kotlin.test.Test class AnthropicTest { @Test - fun shouldReceiveAnIntroductionFromClaude() = runTest { + fun `Should receive an introduction from Claude`() = runTest { // given val client = Anthropic() @@ -41,7 +42,7 @@ class AnthropicTest { } // then - assertSoftly(response) { + response.assert { role shouldBe Role.ASSISTANT model shouldBe "claude-3-5-sonnet-20241022" stopReason shouldBe StopReason.END_TURN @@ -56,25 +57,25 @@ class AnthropicTest { } @Test - fun shouldStreamTheResponse() = runTest { + fun `Should stream the response`() = runTest { // given val client = Anthropic() // when val response = client.messages.stream { - +Message { +"Say: 'The sun slowly dipped below the horizon, painting the sky in a breathtaking array of oranges, pinks, and purples.'" } - } - .filterIsInstance() - .map { (it.delta as TextDelta).text } - .toList() - .joinToString(separator = "") + +Message { +"Say: 'The sun slowly dipped below the horizon, painting the sky in a breathtaking array of oranges, pinks, and purples.'" } + } + .filterIsInstance() + .map { (it.delta as TextDelta).text } + .toList() + .joinToString(separator = "") // then response shouldBe "The sun slowly dipped below the horizon, painting the sky in a breathtaking array of oranges, pinks, and purples." } @Test - fun shouldUseCalculatorTool() = runTest { + fun `Should use Calculator tool`() = runTest { // given val client = Anthropic { tool() @@ -90,7 +91,7 @@ class AnthropicTest { conversation += initialResponse // then - assertSoftly(initialResponse) { + initialResponse.assert { stopReason shouldBe StopReason.TOOL_USE content.size shouldBe 1 // and therefore there is only ToolUse without commentary content[0] shouldBe instanceOf() @@ -109,7 +110,7 @@ class AnthropicTest { } // then - assertSoftly(resultResponse) { + resultResponse.assert { stopReason shouldBe StopReason.END_TURN content.size shouldBe 1 content[0] shouldBe instanceOf() @@ -118,7 +119,7 @@ class AnthropicTest { } @Test - fun shouldUseFibonacciTool() = runTest { + fun `Should use FibonacciTool`() = runTest { // given val client = Anthropic { tool() @@ -135,7 +136,7 @@ class AnthropicTest { toolUse.name shouldBe "FibonacciTool" val result = toolUse.use() - assertSoftly(result) { + result.assert { toolUseId shouldBe toolUse.id isError shouldBe false content shouldBe listOf(Text(text = "267914296")) @@ -143,7 +144,8 @@ class AnthropicTest { } @Test - fun shouldUse2ToolsInSequence() = runTest { + @Ignore // this test is flaky because it has wrong sometimes claude will decide to use both tools at once. + fun `Should use 2 tools in sequence`() = runTest { // given val client = Anthropic { tool() @@ -156,7 +158,7 @@ class AnthropicTest { val fibonacciResponse = client.messages.create { messages = conversation - allTools() + singleTool() } conversation += fibonacciResponse @@ -167,7 +169,7 @@ class AnthropicTest { val calculatorResponse = client.messages.create { messages = conversation - allTools() + singleTool() } conversation += calculatorResponse @@ -187,7 +189,7 @@ class AnthropicTest { } @Test - fun shouldUseToolWithDependencies() = runTest { + fun `Should use tool with dependencies`() = runTest { // given val testDatabase = TestDatabase() val anthropic = Anthropic { @@ -212,7 +214,7 @@ class AnthropicTest { } @Test - fun shouldUseSystemPrompt() = runTest { + fun `Should use system prompt`() = runTest { // given val anthropic = Anthropic() @@ -226,7 +228,7 @@ class AnthropicTest { } // then - assertSoftly(response) { + response.assert { content.size shouldBe 1 content[0] shouldBe instanceOf() val text = content[0] as Text diff --git a/src/commonTest/kotlin/content/DocumentTest.kt b/src/commonTest/kotlin/content/DocumentTest.kt index 81c6040..f60944f 100644 --- a/src/commonTest/kotlin/content/DocumentTest.kt +++ b/src/commonTest/kotlin/content/DocumentTest.kt @@ -3,7 +3,7 @@ package com.xemantic.anthropic.content import com.xemantic.anthropic.Anthropic import com.xemantic.anthropic.message.Message import com.xemantic.anthropic.message.StopReason -import io.kotest.assertions.assertSoftly +import com.xemantic.anthropic.test.assert import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import io.kotest.matchers.types.instanceOf @@ -44,7 +44,7 @@ class DocumentTest { } // then - assertSoftly(response) { + response.assert { stopReason shouldBe StopReason.END_TURN content.size shouldBe 1 content[0] shouldBe instanceOf() diff --git a/src/commonTest/kotlin/content/ImageTest.kt b/src/commonTest/kotlin/content/ImageTest.kt index 5b11323..3673579 100644 --- a/src/commonTest/kotlin/content/ImageTest.kt +++ b/src/commonTest/kotlin/content/ImageTest.kt @@ -3,7 +3,7 @@ package com.xemantic.anthropic.content import com.xemantic.anthropic.Anthropic import com.xemantic.anthropic.message.Message import com.xemantic.anthropic.message.StopReason -import io.kotest.assertions.assertSoftly +import com.xemantic.anthropic.test.assert import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import io.kotest.matchers.types.instanceOf @@ -45,7 +45,7 @@ class ImageTest { } // then - assertSoftly(response) { + response.assert { stopReason shouldBe StopReason.END_TURN content.size shouldBe 1 content[0] shouldBe instanceOf() diff --git a/src/commonTest/kotlin/error/ErrorResponseTest.kt b/src/commonTest/kotlin/error/ErrorResponseTest.kt index 284dd6d..199c689 100644 --- a/src/commonTest/kotlin/error/ErrorResponseTest.kt +++ b/src/commonTest/kotlin/error/ErrorResponseTest.kt @@ -1,8 +1,8 @@ package com.xemantic.anthropic.error import com.xemantic.anthropic.Response +import com.xemantic.anthropic.test.assert import com.xemantic.anthropic.test.testJson -import io.kotest.assertions.assertSoftly import io.kotest.matchers.shouldBe import io.kotest.matchers.types.instanceOf import kotlin.test.Test @@ -27,7 +27,7 @@ class ErrorResponseTest { val response = testJson.decodeFromString(jsonResponse) response shouldBe instanceOf() - assertSoftly(response as ErrorResponse) { + (response as ErrorResponse).assert { error shouldBe Error( type = "not_found_error", message = "The requested resource could not be found." diff --git a/src/commonTest/kotlin/message/MessageRequestTest.kt b/src/commonTest/kotlin/message/MessageRequestTest.kt index bdab202..e6db1b1 100644 --- a/src/commonTest/kotlin/message/MessageRequestTest.kt +++ b/src/commonTest/kotlin/message/MessageRequestTest.kt @@ -1,7 +1,6 @@ package com.xemantic.anthropic.message -import com.xemantic.anthropic.message.MessageRequestTest.TemperatureUnit -import com.xemantic.anthropic.schema.Description +import com.xemantic.ai.tool.schema.meta.Description import com.xemantic.anthropic.test.testJson import com.xemantic.anthropic.tool.AnthropicTool import com.xemantic.anthropic.tool.Tool @@ -22,7 +21,6 @@ import kotlin.test.Test data class GetWeather( @Description("The city and state, e.g. San Francisco, CA") val location: String, - @Description("The unit of temperature, either 'celsius' or 'fahrenheit'") val unit: TemperatureUnit? = null ) : ToolInput() { init { @@ -32,6 +30,15 @@ data class GetWeather( } } +@Description("The unit of temperature, either 'celsius' or 'fahrenheit'") +@Suppress("unused") // it is used by the serializer +enum class TemperatureUnit { + @SerialName("celsius") + CELSIUS, + @SerialName("fahrenheit") + FAHRENHEIT +} + /** * Tests the JSON serialization format of created Anthropic API message requests. */ @@ -77,14 +84,6 @@ class MessageRequestTest { """.trimIndent() } - @Suppress("unused") // it is used by the serializer - enum class TemperatureUnit { - @SerialName("celsius") - CELSIUS, - @SerialName("fahrenheit") - FAHRENHEIT - } - @Test fun shouldCreateMessageRequestWithMultipleTools() { // given @@ -107,7 +106,6 @@ class MessageRequestTest { // when val json = testJson.encodeToString(request) - // then json shouldEqualJson """ { "model": "claude-3-5-sonnet-latest", @@ -151,8 +149,8 @@ class MessageRequestTest { }, "unit": { "type": "string", - "enum": ["celsius", "fahrenheit"], - "description": "The unit of temperature, either 'celsius' or 'fahrenheit'" + "description": "The unit of temperature, either 'celsius' or 'fahrenheit'", + "enum": ["celsius", "fahrenheit"] } }, "required": ["location"] @@ -161,6 +159,7 @@ class MessageRequestTest { ] } """.trimIndent() + // then } @Test diff --git a/src/commonTest/kotlin/message/MessageResponseTest.kt b/src/commonTest/kotlin/message/MessageResponseTest.kt index c174e06..6aa51f5 100644 --- a/src/commonTest/kotlin/message/MessageResponseTest.kt +++ b/src/commonTest/kotlin/message/MessageResponseTest.kt @@ -2,9 +2,9 @@ package com.xemantic.anthropic.message import com.xemantic.anthropic.Response import com.xemantic.anthropic.content.ToolUse +import com.xemantic.anthropic.test.assert import com.xemantic.anthropic.test.testJson import com.xemantic.anthropic.usage.Usage -import io.kotest.assertions.assertSoftly import io.kotest.matchers.shouldBe import io.kotest.matchers.types.instanceOf import kotlin.test.Test @@ -46,7 +46,7 @@ class MessageResponseTest { val response = testJson.decodeFromString(jsonResponse) response shouldBe instanceOf() - assertSoftly(response as MessageResponse) { + (response as MessageResponse).assert { id shouldBe "msg_01PspkNzNG3nrf5upeTsmWLF" role shouldBe Role.ASSISTANT model shouldBe "claude-3-5-sonnet-20241022" @@ -60,7 +60,7 @@ class MessageResponseTest { ) } val toolUse = response.content[0] as ToolUse - assertSoftly(toolUse) { + toolUse.assert { id shouldBe "toolu_01YHJK38TBKCRPn7zfjxcKHx" name shouldBe "Calculator" // TODO generate JsonObject to assert input diff --git a/src/commonTest/kotlin/schema/JsonSchemaGeneratorTest.kt b/src/commonTest/kotlin/schema/JsonSchemaGeneratorTest.kt deleted file mode 100644 index be0071e..0000000 --- a/src/commonTest/kotlin/schema/JsonSchemaGeneratorTest.kt +++ /dev/null @@ -1,134 +0,0 @@ -package com.xemantic.anthropic.schema - -import com.xemantic.anthropic.anthropicJson -import io.kotest.assertions.json.shouldEqualJson -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import kotlin.test.Test - -@Serializable -@SerialName("address") -data class Address( - val street: String? = null, - val city: String? = null, - val zipCode: String, - val country: String -) - -@Serializable -data class Person( - @Description("The official name") - val name: String, - val age: Int, - val email: String?, - val hobbies: List = emptyList(), - val address: Address? = null -) - -class JsonSchemaGeneratorTest { - - private val json = Json(from = anthropicJson) { - prettyPrint = true - @OptIn(ExperimentalSerializationApi::class) - prettyPrintIndent = " " - } - - @Test - fun generateJsonSchemaForAddress() { - // when - val schema = jsonSchemaOf
() - val schemaJson = json.encodeToString(schema) - - // then - schemaJson shouldEqualJson """ - { - "type": "object", - "properties": { - "street": { - "type": "string" - }, - "city": { - "type": "string" - }, - "zipCode": { - "type": "string" - }, - "country": { - "type": "string" - } - }, - "required": [ - "zipCode", - "country" - ] - } - """.trimIndent() - } - - @Test - fun generateSchemaForJson() { - // when - val schema = jsonSchemaOf() - val schemaJson = json.encodeToString(schema) - - // then - schemaJson shouldEqualJson """ - { - "type": "object", - "definitions": { - "address": { - "type": "object", - "properties": { - "street": { - "type": "string" - }, - "city": { - "type": "string" - }, - "zipCode": { - "type": "string" - }, - "country": { - "type": "string" - } - }, - "required": [ - "zipCode", - "country" - ] - } - }, - "properties": { - "name": { - "type": "string", - "description": "The official name" - }, - "age": { - "type": "integer" - }, - "email": { - "type": "string" - }, - "hobbies": { - "type": "array", - "items": { - "type": "string" - } - }, - "address": { - "${'$'}ref": "#/definitions/address" - } - }, - "required": [ - "name", - "age", - "email" - ] - } - """.trimIndent() - } - -} diff --git a/src/commonTest/kotlin/test/AnthropicTestSupport.kt b/src/commonTest/kotlin/test/AnthropicTestSupport.kt index 0b693a8..e1eb244 100644 --- a/src/commonTest/kotlin/test/AnthropicTestSupport.kt +++ b/src/commonTest/kotlin/test/AnthropicTestSupport.kt @@ -3,6 +3,9 @@ package com.xemantic.anthropic.test import com.xemantic.anthropic.anthropicJson import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract /** * A pretty JSON printing for testing. It's derived from [anthropicJson], @@ -14,3 +17,17 @@ val testJson = Json(from = anthropicJson) { @OptIn(ExperimentalSerializationApi::class) prettyPrintIndent = " " } + +/** + * Asserts certain conditions on an object of type [T]. + * + * @param T The type of the object to assert against. + * @param block A lambda with receiver that defines the assertions to be performed on the object. + */ +@OptIn(ExperimentalContracts::class) +inline fun T.assert(block: T.() -> Unit) { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + block(this) +} diff --git a/src/commonTest/kotlin/tool/AnthropicTestTools.kt b/src/commonTest/kotlin/tool/AnthropicTestTools.kt index 20bc124..527acc4 100644 --- a/src/commonTest/kotlin/tool/AnthropicTestTools.kt +++ b/src/commonTest/kotlin/tool/AnthropicTestTools.kt @@ -1,6 +1,6 @@ package com.xemantic.anthropic.tool -import com.xemantic.anthropic.schema.Description +import com.xemantic.ai.tool.schema.meta.Description import kotlinx.serialization.Transient tailrec fun fibonacci( diff --git a/src/commonTest/kotlin/tool/ToolInputTest.kt b/src/commonTest/kotlin/tool/ToolInputTest.kt index abd5750..dc35828 100644 --- a/src/commonTest/kotlin/tool/ToolInputTest.kt +++ b/src/commonTest/kotlin/tool/ToolInputTest.kt @@ -1,20 +1,23 @@ package com.xemantic.anthropic.tool +import com.xemantic.ai.tool.schema.meta.Description import com.xemantic.anthropic.cache.CacheControl -import com.xemantic.anthropic.schema.Description -import com.xemantic.anthropic.schema.JsonSchema -import com.xemantic.anthropic.schema.JsonSchemaProperty -import io.kotest.assertions.assertSoftly -import io.kotest.assertions.throwables.shouldThrowWithMessage +import com.xemantic.anthropic.test.assert +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldMatch import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException import kotlin.test.Test class ToolInputTest { + /** + * Let's start with defining a test tool used later in the tests. + */ @AnthropicTool("TestTool") - @Description("Test tool receiving a message and outputting it back") + @Description("A test tool receiving a message and outputting it back") class TestToolInput( @Description("the message") val message: String @@ -27,42 +30,56 @@ class ToolInputTest { } @Test - fun shouldCreateToolFromUsableToolAnnotatedWithAnthropicTool() { + fun `Should create a tool instance from the test tool annotated with AnthropicTool`() { // when val tool = Tool() - assertSoftly(tool) { + tool.assert { name shouldBe "TestTool" - description shouldBe "Test tool receiving a message and outputting it back" - inputSchema shouldBe JsonSchema( - properties = mapOf("message" to JsonSchemaProperty( - type = "string", - description = "the message" - )), - required = listOf("message") - ) + description shouldBe "A test tool receiving a message and outputting it back" + inputSchema.toString() shouldEqualJson """ + { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "the message" + } + }, + "required": [ + "message" + ] + } + """ cacheControl shouldBe null } } - // TODO maybe we need a builder here? @Test - fun shouldCreateToolWithCacheControlFromUsableToolSuppliedWithCacheControl() { + fun `Should create a tool instance from the test tool with given cacheControl`() { // when + // TODO we need a builder here? val tool = Tool( cacheControl = CacheControl(type = CacheControl.Type.EPHEMERAL) ) - assertSoftly(tool) { + tool.assert { name shouldBe "TestTool" - description shouldBe "Test tool receiving a message and outputting it back" - inputSchema shouldBe JsonSchema( - properties = mapOf("message" to JsonSchemaProperty( - type = "string", - description = "the message" - )), - required = listOf("message") - ) + description shouldBe "A test tool receiving a message and outputting it back" + inputSchema.toString() shouldEqualJson """ + { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "the message" + } + }, + "required": [ + "message" + ] + } + """ cacheControl shouldBe CacheControl(type = CacheControl.Type.EPHEMERAL) } } @@ -70,25 +87,21 @@ class ToolInputTest { class NoAnnotationTool : ToolInput() @Test - fun shouldFailToCreateToolWithoutAnthropicToolAnnotation() { - shouldThrowWithMessage( - "Cannot find serializer for class com.xemantic.anthropic.tool.ToolInputTest\$NoAnnotationTool, " + - "make sure that it is annotated with @AnthropicTool and kotlin.serialization plugin is enabled for the project" - ) { + fun `Should fail to create a Tool without AnthropicTool annotation`() { + shouldThrow { Tool() - } + }.message shouldMatch "Cannot find serializer for class .*NoAnnotationTool, " + + "make sure that it is annotated with @AnthropicTool and kotlin.serialization plugin is enabled for the project" } @Serializable class OnlySerializableAnnotationTool : ToolInput() @Test - fun shouldFailToCreateToolWithOnlySerializableAnnotation() { - shouldThrowWithMessage( - "The class com.xemantic.anthropic.tool.ToolInputTest\$OnlySerializableAnnotationTool must be annotated with @AnthropicTool" - ) { + fun `Should fail to create a Tool with only Serializable annotation`() { + shouldThrow { Tool() - } + }.message shouldMatch "The class .*OnlySerializableAnnotationTool must be annotated with @AnthropicTool" } } diff --git a/src/jsMain/kotlin/JsAnthropic.kt b/src/jsMain/kotlin/JsAnthropic.kt index 105722b..b3db428 100644 --- a/src/jsMain/kotlin/JsAnthropic.kt +++ b/src/jsMain/kotlin/JsAnthropic.kt @@ -1,7 +1,7 @@ package com.xemantic.anthropic actual val envApiKey: String? - get() = null + get() = js("process.env.ANTHROPIC_API_KEY") actual val missingApiKeyMessage: String get() = "apiKey is missing, it has to be provided as a parameter." diff --git a/src/jvmTest/kotlin/content/JvmDocumentTest.kt b/src/jvmTest/kotlin/content/JvmDocumentTest.kt index 224069c..e446b2e 100644 --- a/src/jvmTest/kotlin/content/JvmDocumentTest.kt +++ b/src/jvmTest/kotlin/content/JvmDocumentTest.kt @@ -6,12 +6,12 @@ import com.xemantic.anthropic.test.testJson import io.kotest.assertions.json.shouldEqualJson import kotlinx.coroutines.test.runTest import kotlinx.serialization.encodeToString -import org.junit.jupiter.api.Test +import kotlin.test.Test class JvmDocumentTest { @Test - fun shouldSerializePdfToBase64InMessageRequest() = runTest { + fun `Should serialize PDF to Base64 in MessageRequest`() = runTest { val messageRequest = MessageRequest { +Message { +Document("test-data/minimal.pdf") @@ -47,8 +47,7 @@ class JvmDocumentTest { ], "max_tokens": 8182 } - """.trimIndent() - + """ } -} \ No newline at end of file +} diff --git a/src/jvmTest/kotlin/content/MagicNumberTest.kt b/src/jvmTest/kotlin/content/MagicNumberTest.kt index 45da1ad..c493ed2 100644 --- a/src/jvmTest/kotlin/content/MagicNumberTest.kt +++ b/src/jvmTest/kotlin/content/MagicNumberTest.kt @@ -1,13 +1,13 @@ package com.xemantic.anthropic.content import io.kotest.matchers.shouldBe -import org.junit.jupiter.api.Test import java.io.File +import kotlin.test.Test class MagicNumberTest { @Test - fun shouldDetectImageMediaType() { + fun `Should detect file Magic Number`() { File( "test-data/minimal.pdf" ).readBytes().findMagicNumber() shouldBe MagicNumber.PDF diff --git a/src/wasmJsMain/kotlin/WasmJsAnthropic.kt b/src/wasmJsMain/kotlin/WasmJsAnthropic.kt new file mode 100644 index 0000000..105722b --- /dev/null +++ b/src/wasmJsMain/kotlin/WasmJsAnthropic.kt @@ -0,0 +1,7 @@ +package com.xemantic.anthropic + +actual val envApiKey: String? + get() = null + +actual val missingApiKeyMessage: String + get() = "apiKey is missing, it has to be provided as a parameter." diff --git a/webpack.config.d/config.js b/webpack.config.d/config.js new file mode 100644 index 0000000..0b4a18f --- /dev/null +++ b/webpack.config.d/config.js @@ -0,0 +1,7 @@ +var webpack = require("webpack"); + +var definePlugin = new webpack.DefinePlugin({ + 'process.env.ANTHROPIC_API_KEY': JSON.stringify(process.env.ANTHROPIC_API_KEY) +}); + +config.plugins.push(definePlugin);