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).
-[](https://central.sonatype.com/namespace/com.xemantic.anthropic)
+[](https://central.sonatype.com/namespace/com.xemantic.anthropic/anthropic-sdk-kotlin)
[](https://github.com/xemantic/anthropic-sdk-kotlin/releases)
[](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);