feat(sdk): minimal uniffi setup for cross language sdk (#9593)
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "goose-sdk-types"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Shared types for the Goose SDK"
|
||||
|
||||
[dependencies]
|
||||
agent-client-protocol = { workspace = true, features = ["unstable"] }
|
||||
agent-client-protocol-schema = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
schemars = { workspace = true, features = ["derive"] }
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
# Used to provide extras imports for agent-client-protocol
|
||||
ignored = ["agent-client-protocol-schema"]
|
||||
@@ -0,0 +1,8 @@
|
||||
//! Shared types for the Goose SDK.
|
||||
//!
|
||||
//! These wire types are used by both the ACP client/server path and the
|
||||
//! in-process uniffi bindings, keeping a single source of truth for Goose's
|
||||
//! custom `_goose/*` JSON-RPC methods.
|
||||
|
||||
pub mod custom_notifications;
|
||||
pub mod custom_requests;
|
||||
@@ -0,0 +1,2 @@
|
||||
generated
|
||||
examples/uniffi/*.jar
|
||||
@@ -6,14 +6,28 @@ rust-version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Rust SDK for talking to Goose over the Agent Client Protocol (ACP)"
|
||||
description = "Rust SDK for Goose with optional uniffi bindings for Python/Kotlin"
|
||||
|
||||
[lib]
|
||||
name = "goose_sdk"
|
||||
crate-type = ["cdylib", "staticlib", "rlib"]
|
||||
|
||||
[[bin]]
|
||||
name = "goose-uniffi-bindgen"
|
||||
path = "src/bin/uniffi-bindgen.rs"
|
||||
required-features = ["uniffi"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
uniffi = ["dep:uniffi", "dep:thiserror"]
|
||||
|
||||
[dependencies]
|
||||
goose-sdk-types = { path = "../goose-sdk-types" }
|
||||
agent-client-protocol = { workspace = true, features = ["unstable"] }
|
||||
agent-client-protocol-schema = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
schemars = { workspace = true, features = ["derive"] }
|
||||
|
||||
uniffi = { version = "0.29", features = ["cli"], optional = true }
|
||||
thiserror = { version = "2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# goose-sdk
|
||||
|
||||
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 `aaif_goose` / `aaif.goose`). The published surface is
|
||||
currently a `ping` -> `pong` stub in `src/bindings.rs` — the scaffold for the
|
||||
real implementation.
|
||||
|
||||
```bash
|
||||
just python # build bindings + run examples/uniffi/ping.py
|
||||
just kotlin # build bindings + run examples/uniffi/Ping.kt
|
||||
```
|
||||
|
||||
Both print `pong: aaif.io`.
|
||||
@@ -0,0 +1,9 @@
|
||||
package aaif.example
|
||||
|
||||
import aaif.goose.Client
|
||||
|
||||
fun main() {
|
||||
val client = Client()
|
||||
val pong = client.ping("aaif.io")
|
||||
println(pong.message)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
"""Minimal Goose SDK demo: ping the SDK and print the pong."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(HERE.parent.parent / "generated"))
|
||||
|
||||
from aaif_goose import Client # noqa: E402
|
||||
|
||||
|
||||
def main() -> None:
|
||||
client = Client()
|
||||
pong = client.ping("aaif.io")
|
||||
print(pong.message)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,38 @@
|
||||
set shell := ["bash", "-cu"]
|
||||
set working-directory := '../..'
|
||||
|
||||
lib_ext := if os() == "macos" { "dylib" } else if os() == "windows" { "dll" } else { "so" }
|
||||
lib_dir := "./target/debug"
|
||||
lib_path := lib_dir / "libgoose_sdk." + lib_ext
|
||||
bindgen := "./target/debug/goose-uniffi-bindgen"
|
||||
config := "./crates/goose-sdk/uniffi.toml"
|
||||
gen_dir := "./crates/goose-sdk/generated"
|
||||
examples_dir := "./crates/goose-sdk/examples/uniffi"
|
||||
|
||||
default:
|
||||
@just --list --justfile {{justfile()}}
|
||||
|
||||
_build:
|
||||
cargo build -p goose-sdk --features uniffi -q
|
||||
|
||||
_generate lang: _build
|
||||
{{bindgen}} generate --library {{lib_path}} --config {{config}} --language {{lang}} --no-format --out-dir {{gen_dir}} 2>/dev/null
|
||||
cp {{lib_path}} {{gen_dir}}/
|
||||
touch {{gen_dir}}/__init__.py
|
||||
|
||||
python: (_generate "python")
|
||||
DYLD_LIBRARY_PATH={{lib_dir}} LD_LIBRARY_PATH={{lib_dir}} \
|
||||
python3 {{examples_dir}}/ping.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}}/aaif/goose/aaif_goose.kt \
|
||||
{{examples_dir}}/Ping.kt \
|
||||
-include-runtime -d {{examples_dir}}/ping.jar 2>/dev/null
|
||||
java -Djna.library.path={{lib_dir}} \
|
||||
--enable-native-access=ALL-UNNAMED \
|
||||
-cp {{examples_dir}}/ping.jar:{{examples_dir}}/jna.jar aaif.example.PingKt
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
uniffi::uniffi_bindgen_main()
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//! In-process uniffi bindings for the Goose SDK.
|
||||
//!
|
||||
//! This is the published API surface exposed to Python and Kotlin. Right now it
|
||||
//! is a minimal `ping` -> `pong` round-trip that proves the uniffi
|
||||
//! infrastructure end to end without depending on the `goose` core crate.
|
||||
//!
|
||||
//! To build the real SDK, add `goose` (and whatever else you need) as
|
||||
//! dependencies and replace the [`Client`] methods below with the actual
|
||||
//! agent surface.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Errors surfaced across the uniffi boundary.
|
||||
#[derive(Debug, thiserror::Error, uniffi::Error)]
|
||||
pub enum GooseError {
|
||||
#[error("{0}")]
|
||||
Generic(String),
|
||||
}
|
||||
|
||||
/// A reply to a [`Client::ping`] call.
|
||||
#[derive(Debug, Clone, uniffi::Record)]
|
||||
pub struct Pong {
|
||||
/// Echo of the message that was pinged.
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
/// The top-level entry point for the Goose SDK.
|
||||
///
|
||||
/// This is the object that consuming languages instantiate. Today it only knows
|
||||
/// how to answer a ping; extend it with the real agent API.
|
||||
#[derive(uniffi::Object)]
|
||||
pub struct Client {}
|
||||
|
||||
#[uniffi::export]
|
||||
impl Client {
|
||||
#[uniffi::constructor]
|
||||
pub fn new() -> Arc<Self> {
|
||||
Arc::new(Self {})
|
||||
}
|
||||
|
||||
/// Round-trip a message through the SDK. Returns a [`Pong`] echoing the
|
||||
/// supplied `message`, prefixed with `pong: `.
|
||||
pub fn ping(&self, message: String) -> Result<Pong, GooseError> {
|
||||
if message.is_empty() {
|
||||
return Err(GooseError::Generic("message must not be empty".into()));
|
||||
}
|
||||
Ok(Pong {
|
||||
message: format!("pong: {message}"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ping_returns_pong() {
|
||||
let client = Client::new();
|
||||
let pong = client.ping("aaif.io".into()).expect("ping should succeed");
|
||||
assert_eq!(pong.message, "pong: aaif.io");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_ping_errors() {
|
||||
let client = Client::new();
|
||||
assert!(client.ping(String::new()).is_err());
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,21 @@
|
||||
pub mod custom_notifications;
|
||||
pub mod custom_requests;
|
||||
//! Goose SDK.
|
||||
//!
|
||||
//! With default features this crate re-exports the shared SDK wire types from
|
||||
//! `goose-sdk-types` so you can build an Agent Client Protocol (ACP) client
|
||||
//! that talks to `goose acp` over stdio.
|
||||
//!
|
||||
//! With `--features uniffi` the crate additionally compiles as a
|
||||
//! `cdylib`/`staticlib` and exposes a small in-process API to Python and Kotlin
|
||||
//! via [uniffi-rs](https://github.com/mozilla/uniffi-rs).
|
||||
//!
|
||||
//! The published uniffi surface is intentionally a single `ping` -> `pong`
|
||||
//! round-trip. It exists as a working scaffold for adding the real Goose SDK
|
||||
//! API: replace [`bindings`] with the actual implementation.
|
||||
|
||||
pub use goose_sdk_types::{custom_notifications, custom_requests};
|
||||
|
||||
#[cfg(feature = "uniffi")]
|
||||
uniffi::setup_scaffolding!("aaif_goose");
|
||||
|
||||
#[cfg(feature = "uniffi")]
|
||||
pub mod bindings;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[bindings.kotlin]
|
||||
package_name = "aaif.goose"
|
||||
@@ -122,7 +122,7 @@ strum = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
etcetera = { workspace = true }
|
||||
fs-err = { version = "3.1", default-features = false }
|
||||
goose-sdk = { path = "../goose-sdk", default-features = false }
|
||||
goose-sdk-types = { path = "../goose-sdk-types" }
|
||||
rand = { workspace = true }
|
||||
utoipa = { workspace = true, features = ["chrono"] }
|
||||
tokio-cron-scheduler = { version = "0.15", default-features = false }
|
||||
|
||||
@@ -10,8 +10,7 @@ pub(crate) mod tools;
|
||||
pub mod transport;
|
||||
|
||||
pub use common::{map_permission_response, PermissionDecision};
|
||||
pub use goose_sdk::custom_notifications;
|
||||
pub use goose_sdk::custom_requests;
|
||||
pub use goose_sdk_types::{custom_notifications, custom_requests};
|
||||
pub use provider::{
|
||||
extension_configs_to_mcp_servers, AcpProvider, AcpProviderConfig, ACP_CURRENT_MODEL,
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::sources::parse_frontmatter;
|
||||
use crate::utils::safe_truncate;
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use goose_sdk::custom_requests::{SourceEntry, SourceType};
|
||||
use goose_sdk_types::custom_requests::{SourceEntry, SourceType};
|
||||
use rmcp::model::{
|
||||
CallToolResult, Content, Implementation, InitializeResult, JsonObject, ListToolsResult, Meta,
|
||||
ServerCapabilities, ServerNotification, Tool,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
use crate::sources::parse_frontmatter;
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use goose_sdk::custom_requests::{SourceEntry, SourceType};
|
||||
use goose_sdk_types::custom_requests::{SourceEntry, SourceType};
|
||||
use serde::Deserialize;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fs;
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
compile_error!("Features `rustls-tls` and `native-tls` are mutually exclusive");
|
||||
|
||||
pub mod acp;
|
||||
pub use goose_sdk::custom_notifications;
|
||||
pub use goose_sdk::custom_requests;
|
||||
pub use goose_sdk_types::{custom_notifications, custom_requests};
|
||||
pub mod action_required_manager;
|
||||
pub mod agents;
|
||||
pub mod builtin_extension;
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::agents::extension::PlatformExtensionContext;
|
||||
use crate::agents::mcp_client::{Error, McpClientTrait};
|
||||
use crate::agents::ToolCallContext;
|
||||
use async_trait::async_trait;
|
||||
use goose_sdk::custom_requests::{SourceEntry, SourceType};
|
||||
use goose_sdk_types::custom_requests::{SourceEntry, SourceType};
|
||||
use rmcp::model::{
|
||||
CallToolResult, Content, Implementation, InitializeResult, JsonObject, ListToolsResult,
|
||||
ServerCapabilities, ServerNotification, Tool,
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::sources::parse_frontmatter;
|
||||
use agent_client_protocol::Error;
|
||||
use anyhow::Result;
|
||||
use arguments::apply_skill_arguments;
|
||||
use goose_sdk::custom_requests::{SourceEntry, SourceType};
|
||||
use goose_sdk_types::custom_requests::{SourceEntry, SourceType};
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::path::Path;
|
||||
|
||||
use goose_sdk::custom_requests::{SourceEntry, SourceType};
|
||||
use goose_sdk_types::custom_requests::{SourceEntry, SourceType};
|
||||
|
||||
use super::types::{SlashCommandEntry, SlashCommandSource};
|
||||
use super::util::normalize_command_name;
|
||||
@@ -82,7 +82,7 @@ pub(super) fn commands_from_sources(sources: Vec<SourceEntry>) -> Vec<SlashComma
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use goose_sdk::custom_requests::SourceType;
|
||||
use goose_sdk_types::custom_requests::SourceType;
|
||||
use std::collections::HashMap;
|
||||
use tempfile::TempDir;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::skills::{
|
||||
use crate::source_roots::SourceRoot;
|
||||
use agent_client_protocol::Error;
|
||||
use fs_err as fs;
|
||||
use goose_sdk::custom_requests::{SourceEntry, SourceType};
|
||||
use goose_sdk_types::custom_requests::{SourceEntry, SourceType};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
Reference in New Issue
Block a user