add build and maven publish for kotlin ffi (#10375)
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
name: Maven SDK
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish:
|
||||
description: "Publish gdk to Maven Central after building"
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-native:
|
||||
name: Build native library (${{ matrix.name }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{ matrix.container || null }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: darwin-aarch64
|
||||
os: macos-14
|
||||
resource-prefix: darwin-aarch64
|
||||
- name: linux-x86-64
|
||||
os: ubuntu-latest
|
||||
container: quay.io/pypa/manylinux_2_28_x86_64@sha256:441c35fdc6ee809ff9260894f8468ab4fea8c15dc880f8700a3f81b7922c1cda
|
||||
resource-prefix: linux-x86-64
|
||||
- name: win32-x86-64
|
||||
os: windows-latest
|
||||
resource-prefix: win32-x86-64
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
|
||||
- name: Set up Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
|
||||
|
||||
- name: Cache Cargo artifacts
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
|
||||
- name: Build native library
|
||||
shell: bash
|
||||
run: cargo build -p goose-sdk --features uniffi --release
|
||||
|
||||
- name: Stage native library
|
||||
shell: bash
|
||||
run: |
|
||||
case "${{ runner.os }}" in
|
||||
Linux) lib="target/release/libgoose_sdk.so" ;;
|
||||
macOS) lib="target/release/libgoose_sdk.dylib" ;;
|
||||
Windows) lib="target/release/goose_sdk.dll" ;;
|
||||
*) echo "unsupported runner OS: ${{ runner.os }}" >&2; exit 1 ;;
|
||||
esac
|
||||
mkdir -p "maven-native/${{ matrix.resource-prefix }}"
|
||||
cp "$lib" "maven-native/${{ matrix.resource-prefix }}/"
|
||||
|
||||
- name: Upload native library artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: goose-sdk-native-${{ matrix.name }}
|
||||
path: maven-native/**
|
||||
if-no-files-found: error
|
||||
|
||||
package:
|
||||
name: Package Maven artifact
|
||||
needs: build-native
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
|
||||
- name: Set up Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
|
||||
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4
|
||||
|
||||
- name: Download native library artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
pattern: goose-sdk-native-*
|
||||
path: native-artifacts
|
||||
merge-multiple: true
|
||||
|
||||
- name: Generate Kotlin bindings
|
||||
shell: bash
|
||||
run: |
|
||||
cargo build -p goose-sdk --features uniffi --release
|
||||
crates/goose-sdk/scripts/prepare-maven-package.sh \
|
||||
target/release/libgoose_sdk.so \
|
||||
linux-x86-64
|
||||
|
||||
- name: Restore downloaded native libraries
|
||||
shell: bash
|
||||
run: cp -R native-artifacts/* crates/goose-sdk/maven/src/main/resources/
|
||||
|
||||
- name: Build Maven publication
|
||||
working-directory: crates/goose-sdk/maven
|
||||
run: gradle --no-daemon publishToMavenLocal
|
||||
|
||||
- name: Stage Maven local artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
version=$(grep -m1 '^version =' crates/goose-sdk/Cargo.toml | sed -E 's/version = "([^"]+)"/\1/')
|
||||
mkdir -p maven-artifacts
|
||||
cp -R "$HOME/.m2/repository/io/github/aaif-goose/gdk/$version" maven-artifacts/
|
||||
|
||||
- name: Upload Maven artifact bundle
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: goose-sdk-maven
|
||||
path: maven-artifacts/**
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
name: Publish to Maven Central
|
||||
needs: package
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.publish
|
||||
environment: maven-central
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
||||
|
||||
- name: Set up Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
|
||||
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4
|
||||
|
||||
- name: Download native library artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
pattern: goose-sdk-native-*
|
||||
path: native-artifacts
|
||||
merge-multiple: true
|
||||
|
||||
- name: Generate Kotlin bindings
|
||||
shell: bash
|
||||
run: |
|
||||
cargo build -p goose-sdk --features uniffi --release
|
||||
crates/goose-sdk/scripts/prepare-maven-package.sh \
|
||||
target/release/libgoose_sdk.so \
|
||||
linux-x86-64
|
||||
|
||||
- name: Restore downloaded native libraries
|
||||
shell: bash
|
||||
run: cp -R native-artifacts/* crates/goose-sdk/maven/src/main/resources/
|
||||
|
||||
- name: Publish to Maven Central
|
||||
working-directory: crates/goose-sdk/maven
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_TOKEN_USERNAME }}
|
||||
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_TOKEN_PASSWORD }}
|
||||
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_SIGNING_KEY }}
|
||||
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_SIGNING_PASSWORD }}
|
||||
run: gradle --no-daemon publishAndReleaseToMavenCentral
|
||||
+11
@@ -84,3 +84,14 @@ result
|
||||
# Goose self-test artifacts
|
||||
gooseselftest/
|
||||
.tasks/
|
||||
|
||||
# Generated goose-sdk Maven bindings and native libraries
|
||||
/crates/goose-sdk/maven/src/main/
|
||||
/crates/goose-sdk/maven/.gradle/
|
||||
/crates/goose-sdk/maven/.kotlin/
|
||||
/crates/goose-sdk/maven/build/
|
||||
|
||||
# Generated goose-sdk Kotlin example build artifacts
|
||||
/crates/goose-sdk/examples/uniffi/kotlin/.gradle/
|
||||
/crates/goose-sdk/examples/uniffi/kotlin/.kotlin/
|
||||
/crates/goose-sdk/examples/uniffi/kotlin/build/
|
||||
|
||||
@@ -4,13 +4,13 @@ The bindings layer for Goose. It houses the shared types used for both ACP and
|
||||
SDK access, and exposes a cross-language version of the Goose API.
|
||||
|
||||
With `--features uniffi` the crate compiles to native bindings for Python and
|
||||
Kotlin (namespace `goose` / `io.aaif.goose`). The UniFFI surface currently lets
|
||||
Kotlin (namespace `goose` / `io.github.aaif_goose`). The UniFFI surface currently lets
|
||||
callers construct declarative providers from JSON and stream provider
|
||||
completions.
|
||||
|
||||
```bash
|
||||
just python # build bindings + run examples/uniffi/provider.py
|
||||
just kotlin # build bindings + run examples/uniffi/Provider.kt
|
||||
just kotlin # build the Maven artifact + run examples/uniffi/kotlin
|
||||
```
|
||||
|
||||
## Python package
|
||||
@@ -24,3 +24,16 @@ just --justfile crates/goose-sdk/justfile python-wheel
|
||||
|
||||
This regenerates the UniFFI Python bindings, copies the release native library
|
||||
into the package, and writes the wheel to `crates/goose-sdk/python/dist/`.
|
||||
|
||||
## Maven package
|
||||
|
||||
The Maven Central artifact is published as `io.github.aaif-goose:gdk` and uses
|
||||
the Rust crate version from `crates/goose-sdk/Cargo.toml`.
|
||||
|
||||
```bash
|
||||
just --justfile crates/goose-sdk/justfile maven-package
|
||||
```
|
||||
|
||||
This regenerates the UniFFI Kotlin bindings and packages them with the native
|
||||
library in a JVM jar. CI builds the native libraries for supported platforms and
|
||||
can optionally publish the combined artifact to Maven Central.
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package aaif.example
|
||||
|
||||
import io.aaif.goose.DeclarativeProvider
|
||||
import io.aaif.goose.MessageRole
|
||||
import io.aaif.goose.ProviderMessage
|
||||
import io.aaif.goose.ProviderModelConfig
|
||||
import java.nio.file.Paths
|
||||
|
||||
fun main() {
|
||||
val examplesDir = Paths.get("crates/goose-sdk/examples")
|
||||
val provider = DeclarativeProvider.fromJson(examplesDir.resolve("deepseek.json").toFile().readText())
|
||||
val model = ProviderModelConfig(modelName = "deepseek-v4-flash")
|
||||
val messages = listOf(
|
||||
ProviderMessage(
|
||||
role = MessageRole.USER,
|
||||
text = "what is the capital of France?",
|
||||
),
|
||||
)
|
||||
val stream = provider.stream(
|
||||
model,
|
||||
"You are a knowledgable geography expert",
|
||||
messages,
|
||||
)
|
||||
|
||||
while (true) {
|
||||
val chunk = stream.next() ?: break
|
||||
chunk.text?.let { print(it) }
|
||||
chunk.usageJson?.let { println("\nusage: $it") }
|
||||
}
|
||||
println()
|
||||
}
|
||||
@@ -6,16 +6,26 @@ These examples exercise the in-process Goose SDK UniFFI bindings from Python and
|
||||
|
||||
```bash
|
||||
source bin/activate-hermit
|
||||
```
|
||||
|
||||
The Python example uses the declarative DeepSeek provider:
|
||||
|
||||
```bash
|
||||
export DEEPSEEK_API_KEY=...
|
||||
```
|
||||
|
||||
The Kotlin/JVM Maven smoke test uses the native OpenAI provider:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY=...
|
||||
```
|
||||
|
||||
## Generate bindings
|
||||
|
||||
Regenerate the Python and Kotlin bindings before running the examples:
|
||||
Regenerate the Python bindings before running the Python example:
|
||||
|
||||
```bash
|
||||
just --justfile crates/goose-sdk/justfile _generate python
|
||||
just --justfile crates/goose-sdk/justfile _generate kotlin
|
||||
```
|
||||
|
||||
This writes generated bindings and the debug native library under `crates/goose-sdk/generated/`.
|
||||
@@ -27,27 +37,22 @@ DYLD_LIBRARY_PATH=target/debug LD_LIBRARY_PATH=target/debug \
|
||||
uv run --script crates/goose-sdk/examples/uniffi/provider.py
|
||||
```
|
||||
|
||||
## Kotlin provider example
|
||||
## Kotlin/JVM Maven smoke test
|
||||
|
||||
Download JNA if it is not already present:
|
||||
Build the local Maven artifact and run the downstream smoke test app:
|
||||
|
||||
```bash
|
||||
curl -sSL -o crates/goose-sdk/examples/uniffi/jna.jar \
|
||||
https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.14.0/jna-5.14.0.jar
|
||||
just --justfile crates/goose-sdk/justfile maven-package
|
||||
cd crates/goose-sdk/examples/uniffi/kotlin
|
||||
gradle --no-daemon run
|
||||
```
|
||||
|
||||
Compile and run:
|
||||
Or run the same flow through the goose-sdk justfile:
|
||||
|
||||
```bash
|
||||
kotlinc -cp crates/goose-sdk/examples/uniffi/jna.jar -nowarn \
|
||||
crates/goose-sdk/generated/io/aaif/goose/goose.kt \
|
||||
crates/goose-sdk/examples/uniffi/Provider.kt \
|
||||
-include-runtime -d crates/goose-sdk/examples/uniffi/provider.jar
|
||||
|
||||
java -Djna.library.path=target/debug \
|
||||
--enable-native-access=ALL-UNNAMED \
|
||||
-cp crates/goose-sdk/examples/uniffi/provider.jar:crates/goose-sdk/examples/uniffi/jna.jar \
|
||||
aaif.example.ProviderKt
|
||||
just --justfile crates/goose-sdk/justfile kotlin
|
||||
```
|
||||
|
||||
On Linux, use the same command; `LD_LIBRARY_PATH=target/debug` can also be set if needed. On macOS, `-Djna.library.path=target/debug` is usually enough, but `DYLD_LIBRARY_PATH=target/debug` can also be set if JNA cannot find `libgoose_sdk.dylib`.
|
||||
The Kotlin example consumes the local Maven artifact `io.github.aaif-goose:gdk` from `mavenLocal()` and imports the generated package namespace `io.github.aaif_goose`.
|
||||
|
||||
On newer JDKs, the example enables native access with `--enable-native-access=ALL-UNNAMED` because the SDK uses JNA to load the bundled native library.
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Kotlin/JVM GDK smoke test
|
||||
|
||||
This is a small downstream Kotlin/JVM app that consumes the Maven artifact
|
||||
`io.github.aaif-goose:gdk` from `mavenLocal()`.
|
||||
|
||||
From the repository root, first build and publish the Maven artifact locally:
|
||||
|
||||
```bash
|
||||
source bin/activate-hermit
|
||||
just --justfile crates/goose-sdk/justfile maven-package
|
||||
```
|
||||
|
||||
Then run the smoke test:
|
||||
|
||||
```bash
|
||||
cd crates/goose-sdk/examples/uniffi/kotlin
|
||||
gradle --no-daemon run
|
||||
```
|
||||
|
||||
Set `DATABRICKS_HOST` and `DATABRICKS_TOKEN` before running the example.
|
||||
`DATABRICKS_HOST` should be the Databricks workspace URL, for example
|
||||
`https://dbc-xxxxxxxx-xxxx.cloud.databricks.com`. The example uses the native
|
||||
GDK `DatabricksProvider`, not the declarative JSON provider. The expected output
|
||||
is a streamed completion from Databricks followed by optional usage metadata.
|
||||
The important failure to watch for is `UnsatisfiedLinkError` or a missing native
|
||||
library resource, which would mean the bundled native library was not loaded
|
||||
correctly.
|
||||
|
||||
The example sets `--enable-native-access=ALL-UNNAMED` because JNA loads the
|
||||
bundled Goose native library. Newer JDKs warn when native access is not enabled
|
||||
explicitly, and future JDKs may require it.
|
||||
@@ -0,0 +1,34 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "2.2.21"
|
||||
application
|
||||
}
|
||||
|
||||
fun gooseSdkVersion(): String {
|
||||
val cargoToml = file("../../../Cargo.toml").readText()
|
||||
return Regex("(?m)^version\\s*=\\s*\"([^\"]+)\"")
|
||||
.find(cargoToml)
|
||||
?.groupValues
|
||||
?.get(1)
|
||||
?: error("Could not find goose-sdk version in ../../../Cargo.toml")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("io.github.aaif-goose:gdk:${gooseSdkVersion()}")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("MainKt")
|
||||
applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED")
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "goose-sdk-kotlin-smoke-test"
|
||||
@@ -0,0 +1,35 @@
|
||||
import io.github.aaif_goose.MessageRole
|
||||
import io.github.aaif_goose.ProviderMessage
|
||||
import io.github.aaif_goose.ProviderModelConfig
|
||||
import io.github.aaif_goose.streamFlow
|
||||
import io.github.aaif_goose.providers.openai.defaultModel
|
||||
import io.github.aaif_goose.providers.openai.provider as openAiProvider
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
fun main() = runBlocking {
|
||||
val apiKey = System.getenv("OPENAI_API_KEY")
|
||||
require(!apiKey.isNullOrBlank()) {
|
||||
"Set OPENAI_API_KEY before running this example."
|
||||
}
|
||||
|
||||
val provider = openAiProvider(apiKey)
|
||||
val model = ProviderModelConfig(modelName = defaultModel())
|
||||
val messages = listOf(
|
||||
ProviderMessage(
|
||||
role = MessageRole.USER,
|
||||
text = "What is the capital of France? Answer in one sentence.",
|
||||
),
|
||||
)
|
||||
|
||||
provider
|
||||
.streamFlow(
|
||||
model,
|
||||
"You are a knowledgeable geography expert.",
|
||||
messages,
|
||||
)
|
||||
.collect { chunk ->
|
||||
chunk.text?.let { print(it) }
|
||||
chunk.usageJson?.let { println("\nusage: $it") }
|
||||
}
|
||||
println()
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env -S uv run --script
|
||||
"""Goose SDK demo: build a declarative provider and stream a completion."""
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -8,24 +9,24 @@ HERE = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(HERE.parent.parent / "generated"))
|
||||
|
||||
from goose import ( # noqa: E402
|
||||
DeclarativeProvider,
|
||||
MessageRole,
|
||||
ProviderMessage,
|
||||
ProviderModelConfig,
|
||||
declarative_provider_from_json,
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
provider = DeclarativeProvider.from_json((HERE.parent / "deepseek.json").read_text())
|
||||
async def main() -> None:
|
||||
provider = declarative_provider_from_json((HERE.parent / "deepseek.json").read_text())
|
||||
model = ProviderModelConfig(model_name="deepseek-v4-flash")
|
||||
messages = [ProviderMessage(role=MessageRole.USER, text="what is the capital of France?")]
|
||||
stream = provider.stream(
|
||||
stream = await provider.stream(
|
||||
model,
|
||||
"You are a knowledgable geography expert",
|
||||
messages,
|
||||
)
|
||||
|
||||
while chunk := stream.next():
|
||||
while chunk := await stream.next():
|
||||
if chunk.text:
|
||||
print(chunk.text, end="")
|
||||
if chunk.usage_json:
|
||||
@@ -35,4 +36,4 @@ def main() -> None:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
asyncio.run(main())
|
||||
|
||||
+20
-12
@@ -15,6 +15,9 @@ gen_dir := "./crates/goose-sdk/generated"
|
||||
examples_dir := "./crates/goose-sdk/examples/uniffi"
|
||||
python_dir := "./crates/goose-sdk/python"
|
||||
python_package_dir := python_dir / "src/goose"
|
||||
maven_dir := "./crates/goose-sdk/maven"
|
||||
maven_resources_dir := maven_dir / "src/main/resources"
|
||||
maven_kotlin_dir := maven_dir / "src/main/kotlin"
|
||||
|
||||
_default:
|
||||
@just --list --justfile {{justfile()}}
|
||||
@@ -34,18 +37,8 @@ python: (_generate "python")
|
||||
DYLD_LIBRARY_PATH={{debug_lib_dir}} LD_LIBRARY_PATH={{debug_lib_dir}} \
|
||||
python3 {{examples_dir}}/provider.py
|
||||
|
||||
kotlin: (_generate "kotlin")
|
||||
@if [ ! -f {{examples_dir}}/jna.jar ]; then \
|
||||
curl -sSL -o {{examples_dir}}/jna.jar \
|
||||
https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.14.0/jna-5.14.0.jar; \
|
||||
fi
|
||||
kotlinc -cp {{examples_dir}}/jna.jar -nowarn \
|
||||
{{gen_dir}}/io/aaif/goose/goose.kt \
|
||||
{{examples_dir}}/Provider.kt \
|
||||
-include-runtime -d {{examples_dir}}/provider.jar 2>/dev/null
|
||||
java -Djna.library.path={{debug_lib_dir}} \
|
||||
--enable-native-access=ALL-UNNAMED \
|
||||
-cp {{examples_dir}}/provider.jar:{{examples_dir}}/jna.jar aaif.example.ProviderKt
|
||||
kotlin: maven-package
|
||||
cd {{examples_dir}}/kotlin && gradle --no-daemon run
|
||||
|
||||
python-bindings profile="debug":
|
||||
@case "{{profile}}" in \
|
||||
@@ -76,6 +69,20 @@ python-publish repository="pypi": python-wheel
|
||||
UV_NO_CONFIG=1 PIP_CONFIG_FILE=/dev/null PIP_INDEX_URL=https://pypi.org/simple uvx --default-index https://pypi.org/simple twine upload --repository {{repository}} {{python_dir}}/dist/*.whl; \
|
||||
fi
|
||||
|
||||
maven-bindings profile="release":
|
||||
@case "{{profile}}" in \
|
||||
debug) cargo build -p goose-sdk --features uniffi -q; lib_path={{debug_lib_path}} ;; \
|
||||
release) cargo build -p goose-sdk --features uniffi --release -q; lib_path={{release_lib_path}} ;; \
|
||||
*) echo 'profile must be debug or release' >&2; exit 1 ;; \
|
||||
esac; \
|
||||
crates/goose-sdk/scripts/prepare-maven-package.sh "$lib_path"
|
||||
|
||||
maven-package: (maven-bindings "release")
|
||||
cd {{maven_dir}} && ./gradlew --no-daemon publishToMavenLocal
|
||||
|
||||
maven-publish: (maven-bindings "release")
|
||||
cd {{maven_dir}} && ./gradlew --no-daemon publishAndReleaseToMavenCentral
|
||||
|
||||
crates-publish dry_run="true":
|
||||
@set -euo pipefail; \
|
||||
dry_run_flag=""; \
|
||||
@@ -153,5 +160,6 @@ bump-version rust_version:
|
||||
|
||||
print(f"Rust crates: {rust_version}")
|
||||
print(f"Python package: {python_version}")
|
||||
print(f"Maven artifact: {rust_version}")
|
||||
PY
|
||||
cargo fmt --all
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Goose SDK Maven package
|
||||
|
||||
This project packages the UniFFI-generated Kotlin/JVM bindings for `goose-sdk`
|
||||
as the Maven artifact `io.github.aaif-goose:gdk`.
|
||||
|
||||
The artifact version is read from `crates/goose-sdk/Cargo.toml`, so it stays in
|
||||
lockstep with the Rust crate version. The jar includes the generated Kotlin API
|
||||
and native libraries under JNA platform resource directories.
|
||||
|
||||
Build locally from the repository root:
|
||||
|
||||
```bash
|
||||
just --justfile crates/goose-sdk/justfile maven-package
|
||||
```
|
||||
|
||||
Publish to Maven Central from the repository root:
|
||||
|
||||
```bash
|
||||
just --justfile crates/goose-sdk/justfile maven-publish
|
||||
```
|
||||
|
||||
Publishing requires the standard Gradle properties used by
|
||||
`com.vanniktech.maven.publish` for Maven Central credentials and in-memory PGP
|
||||
signing, for example via environment variables:
|
||||
|
||||
- `ORG_GRADLE_PROJECT_mavenCentralUsername`
|
||||
- `ORG_GRADLE_PROJECT_mavenCentralPassword`
|
||||
- `ORG_GRADLE_PROJECT_signingInMemoryKey`
|
||||
- `ORG_GRADLE_PROJECT_signingInMemoryKeyPassword`
|
||||
@@ -0,0 +1,86 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "2.2.21"
|
||||
`java-library`
|
||||
id("com.vanniktech.maven.publish") version "0.34.0"
|
||||
}
|
||||
|
||||
group = "io.github.aaif-goose"
|
||||
version = gooseSdkVersion()
|
||||
|
||||
fun gooseSdkVersion(): String {
|
||||
val cargoToml = file("../Cargo.toml").readText()
|
||||
return Regex("(?m)^version\\s*=\\s*\"([^\"]+)\"")
|
||||
.find(cargoToml)
|
||||
?.groupValues
|
||||
?.get(1)
|
||||
?: error("Could not find goose-sdk version in ../Cargo.toml")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api("net.java.dev.jna:jna:5.14.0")
|
||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
manifest {
|
||||
attributes(
|
||||
"Implementation-Title" to "Goose SDK",
|
||||
"Implementation-Version" to project.version,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<GenerateModuleMetadata>().configureEach {
|
||||
dependsOn(tasks.named("plainJavadocJar"))
|
||||
}
|
||||
|
||||
mavenPublishing {
|
||||
publishToMavenCentral(automaticRelease = true)
|
||||
if (providers.gradleProperty("signingInMemoryKey").isPresent) {
|
||||
signAllPublications()
|
||||
}
|
||||
|
||||
coordinates(
|
||||
groupId = "io.github.aaif-goose",
|
||||
artifactId = "gdk",
|
||||
version = project.version.toString(),
|
||||
)
|
||||
|
||||
pom {
|
||||
name.set("Goose GDK")
|
||||
description.set("Kotlin/JVM bindings for the Goose SDK")
|
||||
inceptionYear.set("2026")
|
||||
url.set("https://github.com/aaif-goose/goose")
|
||||
licenses {
|
||||
license {
|
||||
name.set("Apache License, Version 2.0")
|
||||
url.set("https://www.apache.org/licenses/LICENSE-2.0")
|
||||
distribution.set("repo")
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id.set("aaif")
|
||||
name.set("Agentic AI Foundation")
|
||||
email.set("ai-oss-tools@block.xyz")
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection.set("scm:git:https://github.com/aaif-goose/goose.git")
|
||||
developerConnection.set("scm:git:ssh://git@github.com/aaif-goose/goose.git")
|
||||
url.set("https://github.com/aaif-goose/goose")
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if command -v gradle >/dev/null 2>&1; then
|
||||
exec gradle "$@"
|
||||
fi
|
||||
|
||||
echo "gradle is required to build the goose-sdk Maven package" >&2
|
||||
echo "Install gradle or run inside the Hermit environment." >&2
|
||||
exit 1
|
||||
@@ -0,0 +1,15 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "goose-sdk-maven"
|
||||
@@ -0,0 +1,45 @@
|
||||
package io.github.aaif_goose
|
||||
|
||||
import com.sun.jna.Platform
|
||||
import java.nio.file.Files
|
||||
|
||||
internal object NativeLibraryLoader {
|
||||
init {
|
||||
val componentName = "goose"
|
||||
if (System.getProperty("uniffi.component.$componentName.libraryOverride") == null) {
|
||||
val resource = nativeResourcePath()
|
||||
val stream = NativeLibraryLoader::class.java.classLoader.getResourceAsStream(resource)
|
||||
?: error("Goose SDK native library resource not found: $resource")
|
||||
val library = Files.createTempFile("goose-sdk-", nativeLibraryFileName()).toFile()
|
||||
library.deleteOnExit()
|
||||
stream.use { input -> library.outputStream().use { output -> input.copyTo(output) } }
|
||||
System.setProperty("uniffi.component.$componentName.libraryOverride", library.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
fun ensureLoaded() = Unit
|
||||
|
||||
private fun nativeResourcePath(): String = "${jnaResourcePrefix()}/${nativeLibraryFileName()}"
|
||||
|
||||
private fun nativeLibraryFileName(): String = when (osName()) {
|
||||
"darwin" -> "libgoose_sdk.dylib"
|
||||
"linux" -> "libgoose_sdk.so"
|
||||
"win32" -> "goose_sdk.dll"
|
||||
else -> error("Unsupported OS: ${System.getProperty("os.name")}")
|
||||
}
|
||||
|
||||
private fun jnaResourcePrefix(): String = "${osName()}-${archName()}"
|
||||
|
||||
private fun osName(): String = when {
|
||||
System.getProperty("os.name").startsWith("Mac OS X") -> "darwin"
|
||||
System.getProperty("os.name").startsWith("Linux") -> "linux"
|
||||
System.getProperty("os.name").startsWith("Windows") -> "win32"
|
||||
else -> System.getProperty("os.name").lowercase().replace(Regex("\\s+"), "-")
|
||||
}
|
||||
|
||||
private fun archName(): String = when {
|
||||
Platform.isARM() && Platform.is64Bit() -> "aarch64"
|
||||
Platform.isIntel() && Platform.is64Bit() -> "x86-64"
|
||||
else -> System.getProperty("os.arch")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.github.aaif_goose
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
public fun Provider.streamFlow(
|
||||
model: ProviderModelConfig,
|
||||
system: String,
|
||||
messages: List<ProviderMessage>,
|
||||
): Flow<ProviderStreamChunk> = flow {
|
||||
val stream = stream(model, system, messages)
|
||||
while (true) {
|
||||
emit(stream.next() ?: break)
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package io.github.aaif_goose.providers.databricks
|
||||
|
||||
public fun provider(host: String, token: String): io.github.aaif_goose.Provider =
|
||||
io.github.aaif_goose.databricksProvider(host, token)
|
||||
|
||||
public fun defaultModel(): String = io.github.aaif_goose.databricksDefaultModel()
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package io.github.aaif_goose.providers.openai
|
||||
|
||||
public fun provider(apiKey: String): io.github.aaif_goose.Provider = io.github.aaif_goose.openaiProvider(apiKey)
|
||||
|
||||
public fun defaultModel(): String = io.github.aaif_goose.openaiDefaultModel()
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
case "$(uname -s)-$(uname -m)" in
|
||||
Darwin-arm64) echo "darwin-aarch64" ;;
|
||||
Darwin-x86_64) echo "darwin-x86-64" ;;
|
||||
Linux-x86_64) echo "linux-x86-64" ;;
|
||||
MINGW64_NT-*-x86_64|MSYS_NT-*-x86_64|CYGWIN_NT-*-x86_64) echo "win32-x86-64" ;;
|
||||
*) echo "unsupported platform: $(uname -s)-$(uname -m)" >&2; exit 1 ;;
|
||||
esac
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -lt 1 ]; then
|
||||
echo "usage: $0 <native-lib> [jna-resource-prefix]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
native_lib="$1"
|
||||
resource_prefix="${2:-$(crates/goose-sdk/scripts/maven-resource-prefix.sh)}"
|
||||
|
||||
case "$resource_prefix" in
|
||||
darwin-aarch64|linux-x86-64|win32-x86-64) ;;
|
||||
*) echo "unsupported JNA resource prefix: $resource_prefix" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
if [ ! -f "$native_lib" ]; then
|
||||
echo "native library not found: $native_lib" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bindgen="target/release/goose-uniffi-bindgen"
|
||||
if [ ! -x "$bindgen" ]; then
|
||||
cargo build -p goose-sdk --features uniffi --release -q
|
||||
fi
|
||||
|
||||
maven_dir="crates/goose-sdk/maven"
|
||||
kotlin_dir="$maven_dir/src/main/kotlin"
|
||||
support_kotlin_dir="$maven_dir/src/support/kotlin"
|
||||
resources_dir="$maven_dir/src/main/resources"
|
||||
|
||||
rm -rf "$kotlin_dir/io/github/aaif_goose" "$resources_dir/$resource_prefix"
|
||||
mkdir -p "$kotlin_dir" "$resources_dir/$resource_prefix" "$resources_dir/META-INF"
|
||||
cp LICENSE "$resources_dir/META-INF/LICENSE"
|
||||
|
||||
"$bindgen" generate \
|
||||
--library "$native_lib" \
|
||||
--config crates/goose-sdk/uniffi.toml \
|
||||
--language kotlin \
|
||||
--no-format \
|
||||
--out-dir "$kotlin_dir" 2>/dev/null
|
||||
|
||||
python3 - "$kotlin_dir/io/github/aaif_goose/goose.kt" <<'PY'
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(sys.argv[1])
|
||||
text = path.read_text()
|
||||
needle = 'private fun findLibraryName(componentName: String): String {\n'
|
||||
replacement = needle + ' NativeLibraryLoader.ensureLoaded()\n'
|
||||
if needle not in text:
|
||||
raise SystemExit('could not find findLibraryName in generated Kotlin bindings')
|
||||
path.write_text(text.replace(needle, replacement, 1))
|
||||
PY
|
||||
|
||||
cp -R "$support_kotlin_dir"/. "$kotlin_dir"/
|
||||
cp "$native_lib" "$resources_dir/$resource_prefix/"
|
||||
@@ -4,14 +4,18 @@
|
||||
//! on declarative providers: consumers can construct a provider from JSON and
|
||||
//! stream completions from it.
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{future::Future, sync::Arc, sync::OnceLock};
|
||||
|
||||
use futures::StreamExt;
|
||||
use goose_providers::{
|
||||
base::{MessageStream, Provider},
|
||||
api_client::{ApiClient, AuthMethod},
|
||||
base::{MessageStream, Provider as GooseProvider},
|
||||
conversation::message::Message,
|
||||
databricks::DatabricksProvider as GooseDatabricksProvider,
|
||||
databricks_auth::DatabricksAuth,
|
||||
declarative::EnvKeyResolver,
|
||||
model::ModelConfig,
|
||||
openai::OpenAiProviderBuilder,
|
||||
};
|
||||
|
||||
/// Errors surfaced across the uniffi boundary.
|
||||
@@ -113,88 +117,181 @@ pub struct ProviderStreamChunk {
|
||||
pub usage_json: Option<String>,
|
||||
}
|
||||
|
||||
/// A declarative Goose provider constructed from provider JSON.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct DeclarativeProvider {
|
||||
provider: Box<dyn Provider>,
|
||||
runtime: Arc<tokio::runtime::Runtime>,
|
||||
}
|
||||
static RUNTIME: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
|
||||
|
||||
#[uniffi::export]
|
||||
impl DeclarativeProvider {
|
||||
/// Construct a declarative provider using the process environment to resolve
|
||||
/// configured API key environment variables.
|
||||
#[uniffi::constructor]
|
||||
pub fn from_json(json: String) -> Result<Arc<Self>, GooseError> {
|
||||
let provider = goose_providers::declarative::from_json(&json, None, EnvKeyResolver {})?;
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.map_err(|error| GooseError::Generic(error.to_string()))?;
|
||||
|
||||
Ok(Arc::new(Self {
|
||||
provider,
|
||||
runtime: Arc::new(runtime),
|
||||
}))
|
||||
fn runtime() -> Result<&'static tokio::runtime::Runtime, GooseError> {
|
||||
if let Some(runtime) = RUNTIME.get() {
|
||||
return Ok(runtime);
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.map_err(|error| GooseError::Generic(error.to_string()))?;
|
||||
|
||||
let _ = RUNTIME.set(runtime);
|
||||
Ok(RUNTIME.get().expect("runtime was initialized"))
|
||||
}
|
||||
|
||||
async fn run_on_runtime<T>(
|
||||
future: impl Future<Output = T> + Send + 'static,
|
||||
) -> Result<T, GooseError>
|
||||
where
|
||||
T: Send + 'static,
|
||||
{
|
||||
let (sender, receiver) = tokio::sync::oneshot::channel();
|
||||
runtime()?.spawn(async move {
|
||||
let _ = sender.send(future.await);
|
||||
});
|
||||
|
||||
receiver
|
||||
.await
|
||||
.map_err(|_| GooseError::Generic("runtime task was cancelled".to_string()))
|
||||
}
|
||||
|
||||
struct ProviderHandle {
|
||||
provider: Arc<dyn GooseProvider>,
|
||||
}
|
||||
|
||||
impl ProviderHandle {
|
||||
fn new(provider: Box<dyn GooseProvider>) -> Self {
|
||||
Self {
|
||||
provider: Arc::from(provider),
|
||||
}
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
self.provider.get_name().to_string()
|
||||
}
|
||||
|
||||
/// Start a streaming completion request. Tools are not yet exposed over the
|
||||
/// uniffi boundary, so this calls providers with an empty tool list.
|
||||
pub fn stream(
|
||||
async fn stream(
|
||||
&self,
|
||||
model: ProviderModelConfig,
|
||||
system: String,
|
||||
messages: Vec<ProviderMessage>,
|
||||
) -> Result<Arc<DeclarativeProviderStream>, GooseError> {
|
||||
) -> Result<Arc<ProviderStream>, GooseError> {
|
||||
let model = model.to_goose_model_config()?;
|
||||
let messages = messages
|
||||
.iter()
|
||||
.map(ProviderMessage::to_goose_message)
|
||||
.collect::<Vec<_>>();
|
||||
let provider = Arc::clone(&self.provider);
|
||||
let stream =
|
||||
self.runtime
|
||||
.block_on(self.provider.stream(&model, &system, &messages, &[]))?;
|
||||
run_on_runtime(async move { provider.stream(&model, &system, &messages, &[]).await })
|
||||
.await??;
|
||||
|
||||
Ok(Arc::new(DeclarativeProviderStream {
|
||||
stream: Mutex::new(stream),
|
||||
runtime: Arc::clone(&self.runtime),
|
||||
Ok(Arc::new(ProviderStream {
|
||||
stream: Arc::new(tokio::sync::Mutex::new(stream)),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// A blocking iterator over provider stream chunks.
|
||||
/// A Goose provider backed by one of Goose's native provider implementations.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct DeclarativeProviderStream {
|
||||
stream: Mutex<MessageStream>,
|
||||
runtime: Arc<tokio::runtime::Runtime>,
|
||||
pub struct Provider {
|
||||
handle: ProviderHandle,
|
||||
}
|
||||
|
||||
impl Provider {
|
||||
fn new(provider: Box<dyn GooseProvider>) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
handle: ProviderHandle::new(provider),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl DeclarativeProviderStream {
|
||||
impl Provider {
|
||||
pub fn name(&self) -> String {
|
||||
self.handle.name()
|
||||
}
|
||||
|
||||
/// Start a streaming completion request. Tools are not yet exposed over the
|
||||
/// uniffi boundary, so this calls providers with an empty tool list.
|
||||
pub async fn stream(
|
||||
&self,
|
||||
model: ProviderModelConfig,
|
||||
system: String,
|
||||
messages: Vec<ProviderMessage>,
|
||||
) -> Result<Arc<ProviderStream>, GooseError> {
|
||||
self.handle.stream(model, system, messages).await
|
||||
}
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
pub fn declarative_provider_from_json(json: String) -> Result<Arc<Provider>, GooseError> {
|
||||
let provider = goose_providers::declarative::from_json(&json, None, EnvKeyResolver {})?;
|
||||
Ok(Provider::new(provider))
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
pub fn openai_default_model() -> String {
|
||||
goose_providers::openai::OPEN_AI_DEFAULT_MODEL.to_string()
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
pub fn openai_provider(api_key: String) -> Result<Arc<Provider>, GooseError> {
|
||||
let api_client = ApiClient::new_with_tls(
|
||||
"https://api.openai.com".to_string(),
|
||||
AuthMethod::BearerToken(api_key),
|
||||
None,
|
||||
)?;
|
||||
let provider = OpenAiProviderBuilder::new(api_client).build();
|
||||
Ok(Provider::new(Box::new(provider)))
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
pub fn databricks_default_model() -> String {
|
||||
goose_providers::databricks::DATABRICKS_DEFAULT_MODEL.to_string()
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
pub fn databricks_provider(host: String, token: String) -> Result<Arc<Provider>, GooseError> {
|
||||
let retry_config = GooseDatabricksProvider::load_retry_config(|key| std::env::var(key).ok());
|
||||
let provider = GooseDatabricksProvider::new(
|
||||
host,
|
||||
DatabricksAuth::token(token),
|
||||
retry_config,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)?;
|
||||
|
||||
Ok(Provider::new(Box::new(provider)))
|
||||
}
|
||||
|
||||
/// An async iterator over provider stream chunks.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct ProviderStream {
|
||||
stream: Arc<tokio::sync::Mutex<MessageStream>>,
|
||||
}
|
||||
|
||||
#[uniffi::export]
|
||||
impl ProviderStream {
|
||||
/// Return the next stream chunk, or `None` when the stream is exhausted.
|
||||
pub fn next(&self) -> Result<Option<ProviderStreamChunk>, GooseError> {
|
||||
let mut stream = self
|
||||
.stream
|
||||
.lock()
|
||||
.map_err(|_| GooseError::Generic("provider stream lock poisoned".to_string()))?;
|
||||
pub async fn next(&self) -> Result<Option<ProviderStreamChunk>, GooseError> {
|
||||
let stream = Arc::clone(&self.stream);
|
||||
run_on_runtime(async move {
|
||||
let mut stream = stream.lock().await;
|
||||
let Some((message, usage)) = stream.next().await.transpose()? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some((message, usage)) = self.runtime.block_on(stream.next()).transpose()? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let text = message.as_ref().map(Message::as_concat_text);
|
||||
let message_json = message.as_ref().map(serde_json::to_string).transpose()?;
|
||||
let usage_json = usage.as_ref().map(serde_json::to_string).transpose()?;
|
||||
|
||||
let text = message.as_ref().map(Message::as_concat_text);
|
||||
let message_json = message.as_ref().map(serde_json::to_string).transpose()?;
|
||||
let usage_json = usage.as_ref().map(serde_json::to_string).transpose()?;
|
||||
|
||||
Ok(Some(ProviderStreamChunk {
|
||||
text,
|
||||
message_json,
|
||||
usage_json,
|
||||
}))
|
||||
Ok(Some(ProviderStreamChunk {
|
||||
text,
|
||||
message_json,
|
||||
usage_json,
|
||||
}))
|
||||
})
|
||||
.await?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[bindings.kotlin]
|
||||
package_name = "io.aaif.goose"
|
||||
package_name = "io.github.aaif_goose"
|
||||
|
||||
Reference in New Issue
Block a user