From 7b815623515376794ad5cfc216c52a6912883161 Mon Sep 17 00:00:00 2001 From: Salman Mohammed Date: Fri, 9 May 2025 13:09:54 -0400 Subject: [PATCH] [goose-llm] add providerConfig param for exposed LLM functions (#2491) --- .gitignore | 2 + bindings/kotlin/example/Usage.kt | 26 +- bindings/kotlin/uniffi/goose_llm/goose_llm.kt | 131 +- bindings/python/goose_llm.py | 3001 +++++++++++++++++ bindings/python/usage.py | 133 + crates/goose-llm/README.md | 33 +- crates/goose-llm/examples/simple.rs | 33 +- crates/goose-llm/src/completion.rs | 8 +- .../goose-llm/src/extractors/session_name.rs | 24 +- crates/goose-llm/src/extractors/tooltip.rs | 23 +- crates/goose-llm/src/providers/databricks.rs | 81 +- crates/goose-llm/src/providers/factory.rs | 22 +- crates/goose-llm/src/providers/openai.rs | 120 +- crates/goose-llm/src/providers/utils.rs | 12 +- crates/goose-llm/src/types/completion.rs | 55 +- .../goose-llm/tests/extract_session_name.rs | 49 +- crates/goose-llm/tests/extract_tooltip.rs | 51 +- crates/goose-llm/tests/providers_extract.rs | 4 +- 18 files changed, 3589 insertions(+), 219 deletions(-) create mode 100644 bindings/python/goose_llm.py create mode 100644 bindings/python/usage.py diff --git a/.gitignore b/.gitignore index a4ac78943f..3d5ef353eb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +__pycache__ +*.pyc *.jar run_cli.sh tokenizer_files/ diff --git a/bindings/kotlin/example/Usage.kt b/bindings/kotlin/example/Usage.kt index 0f789ee01a..ad28ca2242 100644 --- a/bindings/kotlin/example/Usage.kt +++ b/bindings/kotlin/example/Usage.kt @@ -66,14 +66,23 @@ fun main() = runBlocking { printMessages(msgs) println("---\n") - val sessionName = generateSessionName(msgs) - println("Session Name: $sessionName") + // Setup provider + val providerName = "databricks" + val host = System.getenv("DATABRICKS_HOST") ?: error("DATABRICKS_HOST not set") + val token = System.getenv("DATABRICKS_TOKEN") ?: error("DATABRICKS_TOKEN not set") + val providerConfig = """{"host": "$host", "token": "$token"}""" - val tooltip = generateTooltip(msgs) - println("Tooltip: $tooltip") + println("Provider Name: $providerName") + println("Provider Config: $providerConfig") + + + val sessionName = generateSessionName(providerName, providerConfig, msgs) + println("\nSession Name: $sessionName") + + val tooltip = generateTooltip(providerName, providerConfig, msgs) + println("\nTooltip: $tooltip") // Completion - val provider = "databricks" val modelName = "goose-gpt-4-1" val modelConfig = ModelConfig( modelName, @@ -116,8 +125,9 @@ fun main() = runBlocking { val systemPreamble = "You are a helpful assistant." - val req = CompletionRequest( - provider, + val req = createCompletionRequest( + providerName, + providerConfig, modelConfig, systemPreamble, msgs, @@ -127,4 +137,4 @@ fun main() = runBlocking { val response = completion(req) println("\nCompletion Response:") println(response.message) -} \ No newline at end of file +} diff --git a/bindings/kotlin/uniffi/goose_llm/goose_llm.kt b/bindings/kotlin/uniffi/goose_llm/goose_llm.kt index 76e8197a2a..aac9154b09 100644 --- a/bindings/kotlin/uniffi/goose_llm/goose_llm.kt +++ b/bindings/kotlin/uniffi/goose_llm/goose_llm.kt @@ -769,6 +769,8 @@ internal interface IntegrityCheckingUniffiLib : Library { // Integrity check functions only fun uniffi_goose_llm_checksum_func_completion(): Short + fun uniffi_goose_llm_checksum_func_create_completion_request(): Short + fun uniffi_goose_llm_checksum_func_create_tool_config(): Short fun uniffi_goose_llm_checksum_func_generate_session_name(): Short @@ -821,6 +823,16 @@ internal interface UniffiLib : Library { // FFI functions fun uniffi_goose_llm_fn_func_completion(`req`: RustBuffer.ByValue): Long + fun uniffi_goose_llm_fn_func_create_completion_request( + `providerName`: RustBuffer.ByValue, + `providerConfig`: RustBuffer.ByValue, + `modelConfig`: RustBuffer.ByValue, + `systemPreamble`: RustBuffer.ByValue, + `messages`: RustBuffer.ByValue, + `extensions`: RustBuffer.ByValue, + uniffi_out_err: UniffiRustCallStatus, + ): RustBuffer.ByValue + fun uniffi_goose_llm_fn_func_create_tool_config( `name`: RustBuffer.ByValue, `description`: RustBuffer.ByValue, @@ -829,9 +841,17 @@ internal interface UniffiLib : Library { uniffi_out_err: UniffiRustCallStatus, ): RustBuffer.ByValue - fun uniffi_goose_llm_fn_func_generate_session_name(`messages`: RustBuffer.ByValue): Long + fun uniffi_goose_llm_fn_func_generate_session_name( + `providerName`: RustBuffer.ByValue, + `providerConfig`: RustBuffer.ByValue, + `messages`: RustBuffer.ByValue, + ): Long - fun uniffi_goose_llm_fn_func_generate_tooltip(`messages`: RustBuffer.ByValue): Long + fun uniffi_goose_llm_fn_func_generate_tooltip( + `providerName`: RustBuffer.ByValue, + `providerConfig`: RustBuffer.ByValue, + `messages`: RustBuffer.ByValue, + ): Long fun uniffi_goose_llm_fn_func_print_messages( `messages`: RustBuffer.ByValue, @@ -1067,16 +1087,19 @@ private fun uniffiCheckContractApiVersion(lib: IntegrityCheckingUniffiLib) { @Suppress("UNUSED_PARAMETER") private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) { - if (lib.uniffi_goose_llm_checksum_func_completion() != 55281.toShort()) { + if (lib.uniffi_goose_llm_checksum_func_completion() != 47457.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } + if (lib.uniffi_goose_llm_checksum_func_create_completion_request() != 51008.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_goose_llm_checksum_func_create_tool_config() != 22809.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_goose_llm_checksum_func_generate_session_name() != 61290.toShort()) { + if (lib.uniffi_goose_llm_checksum_func_generate_session_name() != 9810.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } - if (lib.uniffi_goose_llm_checksum_func_generate_tooltip() != 7529.toShort()) { + if (lib.uniffi_goose_llm_checksum_func_generate_tooltip() != 15466.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } if (lib.uniffi_goose_llm_checksum_func_print_messages() != 30278.toShort()) { @@ -1365,50 +1388,6 @@ public object FfiConverterString : FfiConverter { } } -data class CompletionRequest( - var `providerName`: kotlin.String, - var `modelConfig`: ModelConfig, - var `systemPreamble`: kotlin.String, - var `messages`: List, - var `extensions`: List, -) { - companion object -} - -/** - * @suppress - */ -public object FfiConverterTypeCompletionRequest : FfiConverterRustBuffer { - override fun read(buf: ByteBuffer): CompletionRequest = - CompletionRequest( - FfiConverterString.read(buf), - FfiConverterTypeModelConfig.read(buf), - FfiConverterString.read(buf), - FfiConverterSequenceTypeMessage.read(buf), - FfiConverterSequenceTypeExtensionConfig.read(buf), - ) - - override fun allocationSize(value: CompletionRequest) = - ( - FfiConverterString.allocationSize(value.`providerName`) + - FfiConverterTypeModelConfig.allocationSize(value.`modelConfig`) + - FfiConverterString.allocationSize(value.`systemPreamble`) + - FfiConverterSequenceTypeMessage.allocationSize(value.`messages`) + - FfiConverterSequenceTypeExtensionConfig.allocationSize(value.`extensions`) - ) - - override fun write( - value: CompletionRequest, - buf: ByteBuffer, - ) { - FfiConverterString.write(value.`providerName`, buf) - FfiConverterTypeModelConfig.write(value.`modelConfig`, buf) - FfiConverterString.write(value.`systemPreamble`, buf) - FfiConverterSequenceTypeMessage.write(value.`messages`, buf) - FfiConverterSequenceTypeExtensionConfig.write(value.`extensions`, buf) - } -} - data class CompletionResponse( var `message`: Message, var `model`: kotlin.String, @@ -2814,6 +2793,14 @@ public object FfiConverterSequenceTypeToolConfig : FfiConverterRustBuffer, + `extensions`: List, +): CompletionRequest = + FfiConverterTypeCompletionRequest.lift( + uniffiRustCall { _status -> + UniffiLib.INSTANCE.uniffi_goose_llm_fn_func_create_completion_request( + FfiConverterString.lower(`providerName`), + FfiConverterTypeJsonValueFfi.lower(`providerConfig`), + FfiConverterTypeModelConfig.lower(`modelConfig`), + FfiConverterString.lower(`systemPreamble`), + FfiConverterSequenceTypeMessage.lower(`messages`), + FfiConverterSequenceTypeExtensionConfig.lower(`extensions`), + _status, + ) + }, + ) + fun `createToolConfig`( `name`: kotlin.String, `description`: kotlin.String, @@ -2894,9 +2903,17 @@ fun `createToolConfig`( */ @Throws(ProviderException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") -suspend fun `generateSessionName`(`messages`: List): kotlin.String = +suspend fun `generateSessionName`( + `providerName`: kotlin.String, + `providerConfig`: JsonValueFfi, + `messages`: List, +): kotlin.String = uniffiRustCallAsync( - UniffiLib.INSTANCE.uniffi_goose_llm_fn_func_generate_session_name(FfiConverterSequenceTypeMessage.lower(`messages`)), + UniffiLib.INSTANCE.uniffi_goose_llm_fn_func_generate_session_name( + FfiConverterString.lower(`providerName`), + FfiConverterTypeJsonValueFfi.lower(`providerConfig`), + FfiConverterSequenceTypeMessage.lower(`messages`), + ), { future, callback, continuation -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_poll_rust_buffer(future, callback, continuation) }, { future, continuation -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_complete_rust_buffer(future, continuation) }, { future -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_free_rust_buffer(future) }, @@ -2912,9 +2929,17 @@ suspend fun `generateSessionName`(`messages`: List): kotlin.String = */ @Throws(ProviderException::class) @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") -suspend fun `generateTooltip`(`messages`: List): kotlin.String = +suspend fun `generateTooltip`( + `providerName`: kotlin.String, + `providerConfig`: JsonValueFfi, + `messages`: List, +): kotlin.String = uniffiRustCallAsync( - UniffiLib.INSTANCE.uniffi_goose_llm_fn_func_generate_tooltip(FfiConverterSequenceTypeMessage.lower(`messages`)), + UniffiLib.INSTANCE.uniffi_goose_llm_fn_func_generate_tooltip( + FfiConverterString.lower(`providerName`), + FfiConverterTypeJsonValueFfi.lower(`providerConfig`), + FfiConverterSequenceTypeMessage.lower(`messages`), + ), { future, callback, continuation -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_poll_rust_buffer(future, callback, continuation) }, { future, continuation -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_complete_rust_buffer(future, continuation) }, { future -> UniffiLib.INSTANCE.ffi_goose_llm_rust_future_free_rust_buffer(future) }, diff --git a/bindings/python/goose_llm.py b/bindings/python/goose_llm.py new file mode 100644 index 0000000000..6fe87ab7d7 --- /dev/null +++ b/bindings/python/goose_llm.py @@ -0,0 +1,3001 @@ + + +# This file was autogenerated by some hot garbage in the `uniffi` crate. +# Trust me, you don't want to mess with it! + +# Common helper code. +# +# Ideally this would live in a separate .py file where it can be unittested etc +# in isolation, and perhaps even published as a re-useable package. +# +# However, it's important that the details of how this helper code works (e.g. the +# way that different builtin types are passed across the FFI) exactly match what's +# expected by the rust code on the other side of the interface. In practice right +# now that means coming from the exact some version of `uniffi` that was used to +# compile the rust component. The easiest way to ensure this is to bundle the Python +# helpers directly inline like we're doing here. + +from __future__ import annotations +import os +import sys +import ctypes +import enum +import struct +import contextlib +import datetime +import threading +import itertools +import traceback +import typing +import asyncio +import platform + +# Used for default argument values +_DEFAULT = object() # type: typing.Any + + +class _UniffiRustBuffer(ctypes.Structure): + _fields_ = [ + ("capacity", ctypes.c_uint64), + ("len", ctypes.c_uint64), + ("data", ctypes.POINTER(ctypes.c_char)), + ] + + @staticmethod + def default(): + return _UniffiRustBuffer(0, 0, None) + + @staticmethod + def alloc(size): + return _uniffi_rust_call(_UniffiLib.ffi_goose_llm_rustbuffer_alloc, size) + + @staticmethod + def reserve(rbuf, additional): + return _uniffi_rust_call(_UniffiLib.ffi_goose_llm_rustbuffer_reserve, rbuf, additional) + + def free(self): + return _uniffi_rust_call(_UniffiLib.ffi_goose_llm_rustbuffer_free, self) + + def __str__(self): + return "_UniffiRustBuffer(capacity={}, len={}, data={})".format( + self.capacity, + self.len, + self.data[0:self.len] + ) + + @contextlib.contextmanager + def alloc_with_builder(*args): + """Context-manger to allocate a buffer using a _UniffiRustBufferBuilder. + + The allocated buffer will be automatically freed if an error occurs, ensuring that + we don't accidentally leak it. + """ + builder = _UniffiRustBufferBuilder() + try: + yield builder + except: + builder.discard() + raise + + @contextlib.contextmanager + def consume_with_stream(self): + """Context-manager to consume a buffer using a _UniffiRustBufferStream. + + The _UniffiRustBuffer will be freed once the context-manager exits, ensuring that we don't + leak it even if an error occurs. + """ + try: + s = _UniffiRustBufferStream.from_rust_buffer(self) + yield s + if s.remaining() != 0: + raise RuntimeError("junk data left in buffer at end of consume_with_stream") + finally: + self.free() + + @contextlib.contextmanager + def read_with_stream(self): + """Context-manager to read a buffer using a _UniffiRustBufferStream. + + This is like consume_with_stream, but doesn't free the buffer afterwards. + It should only be used with borrowed `_UniffiRustBuffer` data. + """ + s = _UniffiRustBufferStream.from_rust_buffer(self) + yield s + if s.remaining() != 0: + raise RuntimeError("junk data left in buffer at end of read_with_stream") + +class _UniffiForeignBytes(ctypes.Structure): + _fields_ = [ + ("len", ctypes.c_int32), + ("data", ctypes.POINTER(ctypes.c_char)), + ] + + def __str__(self): + return "_UniffiForeignBytes(len={}, data={})".format(self.len, self.data[0:self.len]) + + +class _UniffiRustBufferStream: + """ + Helper for structured reading of bytes from a _UniffiRustBuffer + """ + + def __init__(self, data, len): + self.data = data + self.len = len + self.offset = 0 + + @classmethod + def from_rust_buffer(cls, buf): + return cls(buf.data, buf.len) + + def remaining(self): + return self.len - self.offset + + def _unpack_from(self, size, format): + if self.offset + size > self.len: + raise InternalError("read past end of rust buffer") + value = struct.unpack(format, self.data[self.offset:self.offset+size])[0] + self.offset += size + return value + + def read(self, size): + if self.offset + size > self.len: + raise InternalError("read past end of rust buffer") + data = self.data[self.offset:self.offset+size] + self.offset += size + return data + + def read_i8(self): + return self._unpack_from(1, ">b") + + def read_u8(self): + return self._unpack_from(1, ">B") + + def read_i16(self): + return self._unpack_from(2, ">h") + + def read_u16(self): + return self._unpack_from(2, ">H") + + def read_i32(self): + return self._unpack_from(4, ">i") + + def read_u32(self): + return self._unpack_from(4, ">I") + + def read_i64(self): + return self._unpack_from(8, ">q") + + def read_u64(self): + return self._unpack_from(8, ">Q") + + def read_float(self): + v = self._unpack_from(4, ">f") + return v + + def read_double(self): + return self._unpack_from(8, ">d") + +class _UniffiRustBufferBuilder: + """ + Helper for structured writing of bytes into a _UniffiRustBuffer. + """ + + def __init__(self): + self.rbuf = _UniffiRustBuffer.alloc(16) + self.rbuf.len = 0 + + def finalize(self): + rbuf = self.rbuf + self.rbuf = None + return rbuf + + def discard(self): + if self.rbuf is not None: + rbuf = self.finalize() + rbuf.free() + + @contextlib.contextmanager + def _reserve(self, num_bytes): + if self.rbuf.len + num_bytes > self.rbuf.capacity: + self.rbuf = _UniffiRustBuffer.reserve(self.rbuf, num_bytes) + yield None + self.rbuf.len += num_bytes + + def _pack_into(self, size, format, value): + with self._reserve(size): + # XXX TODO: I feel like I should be able to use `struct.pack_into` here but can't figure it out. + for i, byte in enumerate(struct.pack(format, value)): + self.rbuf.data[self.rbuf.len + i] = byte + + def write(self, value): + with self._reserve(len(value)): + for i, byte in enumerate(value): + self.rbuf.data[self.rbuf.len + i] = byte + + def write_i8(self, v): + self._pack_into(1, ">b", v) + + def write_u8(self, v): + self._pack_into(1, ">B", v) + + def write_i16(self, v): + self._pack_into(2, ">h", v) + + def write_u16(self, v): + self._pack_into(2, ">H", v) + + def write_i32(self, v): + self._pack_into(4, ">i", v) + + def write_u32(self, v): + self._pack_into(4, ">I", v) + + def write_i64(self, v): + self._pack_into(8, ">q", v) + + def write_u64(self, v): + self._pack_into(8, ">Q", v) + + def write_float(self, v): + self._pack_into(4, ">f", v) + + def write_double(self, v): + self._pack_into(8, ">d", v) + + def write_c_size_t(self, v): + self._pack_into(ctypes.sizeof(ctypes.c_size_t) , "@N", v) +# A handful of classes and functions to support the generated data structures. +# This would be a good candidate for isolating in its own ffi-support lib. + +class InternalError(Exception): + pass + +class _UniffiRustCallStatus(ctypes.Structure): + """ + Error runtime. + """ + _fields_ = [ + ("code", ctypes.c_int8), + ("error_buf", _UniffiRustBuffer), + ] + + # These match the values from the uniffi::rustcalls module + CALL_SUCCESS = 0 + CALL_ERROR = 1 + CALL_UNEXPECTED_ERROR = 2 + + @staticmethod + def default(): + return _UniffiRustCallStatus(code=_UniffiRustCallStatus.CALL_SUCCESS, error_buf=_UniffiRustBuffer.default()) + + def __str__(self): + if self.code == _UniffiRustCallStatus.CALL_SUCCESS: + return "_UniffiRustCallStatus(CALL_SUCCESS)" + elif self.code == _UniffiRustCallStatus.CALL_ERROR: + return "_UniffiRustCallStatus(CALL_ERROR)" + elif self.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR: + return "_UniffiRustCallStatus(CALL_UNEXPECTED_ERROR)" + else: + return "_UniffiRustCallStatus()" + +def _uniffi_rust_call(fn, *args): + # Call a rust function + return _uniffi_rust_call_with_error(None, fn, *args) + +def _uniffi_rust_call_with_error(error_ffi_converter, fn, *args): + # Call a rust function and handle any errors + # + # This function is used for rust calls that return Result<> and therefore can set the CALL_ERROR status code. + # error_ffi_converter must be set to the _UniffiConverter for the error class that corresponds to the result. + call_status = _UniffiRustCallStatus.default() + + args_with_error = args + (ctypes.byref(call_status),) + result = fn(*args_with_error) + _uniffi_check_call_status(error_ffi_converter, call_status) + return result + +def _uniffi_check_call_status(error_ffi_converter, call_status): + if call_status.code == _UniffiRustCallStatus.CALL_SUCCESS: + pass + elif call_status.code == _UniffiRustCallStatus.CALL_ERROR: + if error_ffi_converter is None: + call_status.error_buf.free() + raise InternalError("_uniffi_rust_call_with_error: CALL_ERROR, but error_ffi_converter is None") + else: + raise error_ffi_converter.lift(call_status.error_buf) + elif call_status.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR: + # When the rust code sees a panic, it tries to construct a _UniffiRustBuffer + # with the message. But if that code panics, then it just sends back + # an empty buffer. + if call_status.error_buf.len > 0: + msg = _UniffiConverterString.lift(call_status.error_buf) + else: + msg = "Unknown rust panic" + raise InternalError(msg) + else: + raise InternalError("Invalid _UniffiRustCallStatus code: {}".format( + call_status.code)) + +def _uniffi_trait_interface_call(call_status, make_call, write_return_value): + try: + return write_return_value(make_call()) + except Exception as e: + call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR + call_status.error_buf = _UniffiConverterString.lower(repr(e)) + +def _uniffi_trait_interface_call_with_error(call_status, make_call, write_return_value, error_type, lower_error): + try: + try: + return write_return_value(make_call()) + except error_type as e: + call_status.code = _UniffiRustCallStatus.CALL_ERROR + call_status.error_buf = lower_error(e) + except Exception as e: + call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR + call_status.error_buf = _UniffiConverterString.lower(repr(e)) +class _UniffiHandleMap: + """ + A map where inserting, getting and removing data is synchronized with a lock. + """ + + def __init__(self): + # type Handle = int + self._map = {} # type: Dict[Handle, Any] + self._lock = threading.Lock() + self._counter = itertools.count() + + def insert(self, obj): + with self._lock: + handle = next(self._counter) + self._map[handle] = obj + return handle + + def get(self, handle): + try: + with self._lock: + return self._map[handle] + except KeyError: + raise InternalError("_UniffiHandleMap.get: Invalid handle") + + def remove(self, handle): + try: + with self._lock: + return self._map.pop(handle) + except KeyError: + raise InternalError("_UniffiHandleMap.remove: Invalid handle") + + def __len__(self): + return len(self._map) +# Types conforming to `_UniffiConverterPrimitive` pass themselves directly over the FFI. +class _UniffiConverterPrimitive: + @classmethod + def lift(cls, value): + return value + + @classmethod + def lower(cls, value): + return value + +class _UniffiConverterPrimitiveInt(_UniffiConverterPrimitive): + @classmethod + def check_lower(cls, value): + try: + value = value.__index__() + except Exception: + raise TypeError("'{}' object cannot be interpreted as an integer".format(type(value).__name__)) + if not isinstance(value, int): + raise TypeError("__index__ returned non-int (type {})".format(type(value).__name__)) + if not cls.VALUE_MIN <= value < cls.VALUE_MAX: + raise ValueError("{} requires {} <= value < {}".format(cls.CLASS_NAME, cls.VALUE_MIN, cls.VALUE_MAX)) + +class _UniffiConverterPrimitiveFloat(_UniffiConverterPrimitive): + @classmethod + def check_lower(cls, value): + try: + value = value.__float__() + except Exception: + raise TypeError("must be real number, not {}".format(type(value).__name__)) + if not isinstance(value, float): + raise TypeError("__float__ returned non-float (type {})".format(type(value).__name__)) + +# Helper class for wrapper types that will always go through a _UniffiRustBuffer. +# Classes should inherit from this and implement the `read` and `write` static methods. +class _UniffiConverterRustBuffer: + @classmethod + def lift(cls, rbuf): + with rbuf.consume_with_stream() as stream: + return cls.read(stream) + + @classmethod + def lower(cls, value): + with _UniffiRustBuffer.alloc_with_builder() as builder: + cls.write(value, builder) + return builder.finalize() + +# Contains loading, initialization code, and the FFI Function declarations. +# Define some ctypes FFI types that we use in the library + +""" +Function pointer for a Rust task, which a callback function that takes a opaque pointer +""" +_UNIFFI_RUST_TASK = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_int8) + +def _uniffi_future_callback_t(return_type): + """ + Factory function to create callback function types for async functions + """ + return ctypes.CFUNCTYPE(None, ctypes.c_uint64, return_type, _UniffiRustCallStatus) + +def _uniffi_load_indirect(): + """ + This is how we find and load the dynamic library provided by the component. + For now we just look it up by name. + """ + if sys.platform == "darwin": + libname = "lib{}.dylib" + elif sys.platform.startswith("win"): + # As of python3.8, ctypes does not seem to search $PATH when loading DLLs. + # We could use `os.add_dll_directory` to configure the search path, but + # it doesn't feel right to mess with application-wide settings. Let's + # assume that the `.dll` is next to the `.py` file and load by full path. + libname = os.path.join( + os.path.dirname(__file__), + "{}.dll", + ) + else: + # Anything else must be an ELF platform - Linux, *BSD, Solaris/illumos + libname = "lib{}.so" + + libname = libname.format("goose_llm") + path = os.path.join(os.path.dirname(__file__), libname) + lib = ctypes.cdll.LoadLibrary(path) + return lib + +def _uniffi_check_contract_api_version(lib): + # Get the bindings contract version from our ComponentInterface + bindings_contract_version = 29 + # Get the scaffolding contract version by calling the into the dylib + scaffolding_contract_version = lib.ffi_goose_llm_uniffi_contract_version() + if bindings_contract_version != scaffolding_contract_version: + raise InternalError("UniFFI contract version mismatch: try cleaning and rebuilding your project") + +def _uniffi_check_api_checksums(lib): + if lib.uniffi_goose_llm_checksum_func_completion() != 47457: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_goose_llm_checksum_func_create_completion_request() != 51008: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_goose_llm_checksum_func_create_tool_config() != 22809: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_goose_llm_checksum_func_generate_session_name() != 9810: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_goose_llm_checksum_func_generate_tooltip() != 15466: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_goose_llm_checksum_func_print_messages() != 30278: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + +# A ctypes library to expose the extern-C FFI definitions. +# This is an implementation detail which will be called internally by the public API. + +_UniffiLib = _uniffi_load_indirect() +_UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK = ctypes.CFUNCTYPE(None,ctypes.c_uint64,ctypes.c_int8, +) +_UNIFFI_FOREIGN_FUTURE_FREE = ctypes.CFUNCTYPE(None,ctypes.c_uint64, +) +_UNIFFI_CALLBACK_INTERFACE_FREE = ctypes.CFUNCTYPE(None,ctypes.c_uint64, +) +class _UniffiForeignFuture(ctypes.Structure): + _fields_ = [ + ("handle", ctypes.c_uint64), + ("free", _UNIFFI_FOREIGN_FUTURE_FREE), + ] +class _UniffiForeignFutureStructU8(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_uint8), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_U8 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructU8, +) +class _UniffiForeignFutureStructI8(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_int8), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_I8 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructI8, +) +class _UniffiForeignFutureStructU16(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_uint16), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_U16 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructU16, +) +class _UniffiForeignFutureStructI16(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_int16), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_I16 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructI16, +) +class _UniffiForeignFutureStructU32(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_uint32), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_U32 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructU32, +) +class _UniffiForeignFutureStructI32(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_int32), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_I32 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructI32, +) +class _UniffiForeignFutureStructU64(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_uint64), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_U64 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructU64, +) +class _UniffiForeignFutureStructI64(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_int64), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_I64 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructI64, +) +class _UniffiForeignFutureStructF32(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_float), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_F32 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructF32, +) +class _UniffiForeignFutureStructF64(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_double), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_F64 = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructF64, +) +class _UniffiForeignFutureStructPointer(ctypes.Structure): + _fields_ = [ + ("return_value", ctypes.c_void_p), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_POINTER = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructPointer, +) +class _UniffiForeignFutureStructRustBuffer(ctypes.Structure): + _fields_ = [ + ("return_value", _UniffiRustBuffer), + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructRustBuffer, +) +class _UniffiForeignFutureStructVoid(ctypes.Structure): + _fields_ = [ + ("call_status", _UniffiRustCallStatus), + ] +_UNIFFI_FOREIGN_FUTURE_COMPLETE_VOID = ctypes.CFUNCTYPE(None,ctypes.c_uint64,_UniffiForeignFutureStructVoid, +) +_UniffiLib.uniffi_goose_llm_fn_func_completion.argtypes = ( + _UniffiRustBuffer, +) +_UniffiLib.uniffi_goose_llm_fn_func_completion.restype = ctypes.c_uint64 +_UniffiLib.uniffi_goose_llm_fn_func_create_completion_request.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_goose_llm_fn_func_create_completion_request.restype = _UniffiRustBuffer +_UniffiLib.uniffi_goose_llm_fn_func_create_tool_config.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_goose_llm_fn_func_create_tool_config.restype = _UniffiRustBuffer +_UniffiLib.uniffi_goose_llm_fn_func_generate_session_name.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, +) +_UniffiLib.uniffi_goose_llm_fn_func_generate_session_name.restype = ctypes.c_uint64 +_UniffiLib.uniffi_goose_llm_fn_func_generate_tooltip.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, + _UniffiRustBuffer, +) +_UniffiLib.uniffi_goose_llm_fn_func_generate_tooltip.restype = ctypes.c_uint64 +_UniffiLib.uniffi_goose_llm_fn_func_print_messages.argtypes = ( + _UniffiRustBuffer, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_goose_llm_fn_func_print_messages.restype = None +_UniffiLib.ffi_goose_llm_rustbuffer_alloc.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rustbuffer_alloc.restype = _UniffiRustBuffer +_UniffiLib.ffi_goose_llm_rustbuffer_from_bytes.argtypes = ( + _UniffiForeignBytes, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rustbuffer_from_bytes.restype = _UniffiRustBuffer +_UniffiLib.ffi_goose_llm_rustbuffer_free.argtypes = ( + _UniffiRustBuffer, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rustbuffer_free.restype = None +_UniffiLib.ffi_goose_llm_rustbuffer_reserve.argtypes = ( + _UniffiRustBuffer, + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rustbuffer_reserve.restype = _UniffiRustBuffer +_UniffiLib.ffi_goose_llm_rust_future_poll_u8.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_u8.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_u8.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_u8.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_u8.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_u8.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_u8.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_u8.restype = ctypes.c_uint8 +_UniffiLib.ffi_goose_llm_rust_future_poll_i8.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_i8.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_i8.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_i8.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_i8.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_i8.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_i8.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_i8.restype = ctypes.c_int8 +_UniffiLib.ffi_goose_llm_rust_future_poll_u16.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_u16.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_u16.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_u16.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_u16.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_u16.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_u16.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_u16.restype = ctypes.c_uint16 +_UniffiLib.ffi_goose_llm_rust_future_poll_i16.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_i16.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_i16.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_i16.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_i16.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_i16.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_i16.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_i16.restype = ctypes.c_int16 +_UniffiLib.ffi_goose_llm_rust_future_poll_u32.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_u32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_u32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_u32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_u32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_u32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_u32.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_u32.restype = ctypes.c_uint32 +_UniffiLib.ffi_goose_llm_rust_future_poll_i32.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_i32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_i32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_i32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_i32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_i32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_i32.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_i32.restype = ctypes.c_int32 +_UniffiLib.ffi_goose_llm_rust_future_poll_u64.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_u64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_u64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_u64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_u64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_u64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_u64.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_u64.restype = ctypes.c_uint64 +_UniffiLib.ffi_goose_llm_rust_future_poll_i64.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_i64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_i64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_i64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_i64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_i64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_i64.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_i64.restype = ctypes.c_int64 +_UniffiLib.ffi_goose_llm_rust_future_poll_f32.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_f32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_f32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_f32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_f32.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_f32.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_f32.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_f32.restype = ctypes.c_float +_UniffiLib.ffi_goose_llm_rust_future_poll_f64.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_f64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_f64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_f64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_f64.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_f64.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_f64.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_f64.restype = ctypes.c_double +_UniffiLib.ffi_goose_llm_rust_future_poll_pointer.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_pointer.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_pointer.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_pointer.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_pointer.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_pointer.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_pointer.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_pointer.restype = ctypes.c_void_p +_UniffiLib.ffi_goose_llm_rust_future_poll_rust_buffer.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_rust_buffer.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_rust_buffer.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_rust_buffer.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_rust_buffer.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_rust_buffer.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_rust_buffer.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_rust_buffer.restype = _UniffiRustBuffer +_UniffiLib.ffi_goose_llm_rust_future_poll_void.argtypes = ( + ctypes.c_uint64, + _UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK, + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_poll_void.restype = None +_UniffiLib.ffi_goose_llm_rust_future_cancel_void.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_cancel_void.restype = None +_UniffiLib.ffi_goose_llm_rust_future_free_void.argtypes = ( + ctypes.c_uint64, +) +_UniffiLib.ffi_goose_llm_rust_future_free_void.restype = None +_UniffiLib.ffi_goose_llm_rust_future_complete_void.argtypes = ( + ctypes.c_uint64, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.ffi_goose_llm_rust_future_complete_void.restype = None +_UniffiLib.uniffi_goose_llm_checksum_func_completion.argtypes = ( +) +_UniffiLib.uniffi_goose_llm_checksum_func_completion.restype = ctypes.c_uint16 +_UniffiLib.uniffi_goose_llm_checksum_func_create_completion_request.argtypes = ( +) +_UniffiLib.uniffi_goose_llm_checksum_func_create_completion_request.restype = ctypes.c_uint16 +_UniffiLib.uniffi_goose_llm_checksum_func_create_tool_config.argtypes = ( +) +_UniffiLib.uniffi_goose_llm_checksum_func_create_tool_config.restype = ctypes.c_uint16 +_UniffiLib.uniffi_goose_llm_checksum_func_generate_session_name.argtypes = ( +) +_UniffiLib.uniffi_goose_llm_checksum_func_generate_session_name.restype = ctypes.c_uint16 +_UniffiLib.uniffi_goose_llm_checksum_func_generate_tooltip.argtypes = ( +) +_UniffiLib.uniffi_goose_llm_checksum_func_generate_tooltip.restype = ctypes.c_uint16 +_UniffiLib.uniffi_goose_llm_checksum_func_print_messages.argtypes = ( +) +_UniffiLib.uniffi_goose_llm_checksum_func_print_messages.restype = ctypes.c_uint16 +_UniffiLib.ffi_goose_llm_uniffi_contract_version.argtypes = ( +) +_UniffiLib.ffi_goose_llm_uniffi_contract_version.restype = ctypes.c_uint32 + +_uniffi_check_contract_api_version(_UniffiLib) +# _uniffi_check_api_checksums(_UniffiLib) + +# Public interface members begin here. + + +class _UniffiConverterUInt32(_UniffiConverterPrimitiveInt): + CLASS_NAME = "u32" + VALUE_MIN = 0 + VALUE_MAX = 2**32 + + @staticmethod + def read(buf): + return buf.read_u32() + + @staticmethod + def write(value, buf): + buf.write_u32(value) + +class _UniffiConverterInt32(_UniffiConverterPrimitiveInt): + CLASS_NAME = "i32" + VALUE_MIN = -2**31 + VALUE_MAX = 2**31 + + @staticmethod + def read(buf): + return buf.read_i32() + + @staticmethod + def write(value, buf): + buf.write_i32(value) + +class _UniffiConverterInt64(_UniffiConverterPrimitiveInt): + CLASS_NAME = "i64" + VALUE_MIN = -2**63 + VALUE_MAX = 2**63 + + @staticmethod + def read(buf): + return buf.read_i64() + + @staticmethod + def write(value, buf): + buf.write_i64(value) + +class _UniffiConverterFloat(_UniffiConverterPrimitiveFloat): + @staticmethod + def read(buf): + return buf.read_float() + + @staticmethod + def write(value, buf): + buf.write_float(value) + +class _UniffiConverterDouble(_UniffiConverterPrimitiveFloat): + @staticmethod + def read(buf): + return buf.read_double() + + @staticmethod + def write(value, buf): + buf.write_double(value) + +class _UniffiConverterString: + @staticmethod + def check_lower(value): + if not isinstance(value, str): + raise TypeError("argument must be str, not {}".format(type(value).__name__)) + return value + + @staticmethod + def read(buf): + size = buf.read_i32() + if size < 0: + raise InternalError("Unexpected negative string length") + utf8_bytes = buf.read(size) + return utf8_bytes.decode("utf-8") + + @staticmethod + def write(value, buf): + utf8_bytes = value.encode("utf-8") + buf.write_i32(len(utf8_bytes)) + buf.write(utf8_bytes) + + @staticmethod + def lift(buf): + with buf.consume_with_stream() as stream: + return stream.read(stream.remaining()).decode("utf-8") + + @staticmethod + def lower(value): + with _UniffiRustBuffer.alloc_with_builder() as builder: + builder.write(value.encode("utf-8")) + return builder.finalize() + + +class CompletionResponse: + message: "Message" + model: "str" + usage: "Usage" + runtime_metrics: "RuntimeMetrics" + def __init__(self, *, message: "Message", model: "str", usage: "Usage", runtime_metrics: "RuntimeMetrics"): + self.message = message + self.model = model + self.usage = usage + self.runtime_metrics = runtime_metrics + + def __str__(self): + return "CompletionResponse(message={}, model={}, usage={}, runtime_metrics={})".format(self.message, self.model, self.usage, self.runtime_metrics) + + def __eq__(self, other): + if self.message != other.message: + return False + if self.model != other.model: + return False + if self.usage != other.usage: + return False + if self.runtime_metrics != other.runtime_metrics: + return False + return True + +class _UniffiConverterTypeCompletionResponse(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return CompletionResponse( + message=_UniffiConverterTypeMessage.read(buf), + model=_UniffiConverterString.read(buf), + usage=_UniffiConverterTypeUsage.read(buf), + runtime_metrics=_UniffiConverterTypeRuntimeMetrics.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterTypeMessage.check_lower(value.message) + _UniffiConverterString.check_lower(value.model) + _UniffiConverterTypeUsage.check_lower(value.usage) + _UniffiConverterTypeRuntimeMetrics.check_lower(value.runtime_metrics) + + @staticmethod + def write(value, buf): + _UniffiConverterTypeMessage.write(value.message, buf) + _UniffiConverterString.write(value.model, buf) + _UniffiConverterTypeUsage.write(value.usage, buf) + _UniffiConverterTypeRuntimeMetrics.write(value.runtime_metrics, buf) + + +class ExtensionConfig: + name: "str" + instructions: "typing.Optional[str]" + tools: "typing.List[ToolConfig]" + def __init__(self, *, name: "str", instructions: "typing.Optional[str]", tools: "typing.List[ToolConfig]"): + self.name = name + self.instructions = instructions + self.tools = tools + + def __str__(self): + return "ExtensionConfig(name={}, instructions={}, tools={})".format(self.name, self.instructions, self.tools) + + def __eq__(self, other): + if self.name != other.name: + return False + if self.instructions != other.instructions: + return False + if self.tools != other.tools: + return False + return True + +class _UniffiConverterTypeExtensionConfig(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return ExtensionConfig( + name=_UniffiConverterString.read(buf), + instructions=_UniffiConverterOptionalString.read(buf), + tools=_UniffiConverterSequenceTypeToolConfig.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.name) + _UniffiConverterOptionalString.check_lower(value.instructions) + _UniffiConverterSequenceTypeToolConfig.check_lower(value.tools) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.name, buf) + _UniffiConverterOptionalString.write(value.instructions, buf) + _UniffiConverterSequenceTypeToolConfig.write(value.tools, buf) + + +class ImageContent: + data: "str" + mime_type: "str" + def __init__(self, *, data: "str", mime_type: "str"): + self.data = data + self.mime_type = mime_type + + def __str__(self): + return "ImageContent(data={}, mime_type={})".format(self.data, self.mime_type) + + def __eq__(self, other): + if self.data != other.data: + return False + if self.mime_type != other.mime_type: + return False + return True + +class _UniffiConverterTypeImageContent(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return ImageContent( + data=_UniffiConverterString.read(buf), + mime_type=_UniffiConverterString.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.data) + _UniffiConverterString.check_lower(value.mime_type) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.data, buf) + _UniffiConverterString.write(value.mime_type, buf) + + +class Message: + """ + A message to or from an LLM + """ + + role: "Role" + created: "int" + content: "Contents" + def __init__(self, *, role: "Role", created: "int", content: "Contents"): + self.role = role + self.created = created + self.content = content + + def __str__(self): + return "Message(role={}, created={}, content={})".format(self.role, self.created, self.content) + + def __eq__(self, other): + if self.role != other.role: + return False + if self.created != other.created: + return False + if self.content != other.content: + return False + return True + +class _UniffiConverterTypeMessage(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return Message( + role=_UniffiConverterTypeRole.read(buf), + created=_UniffiConverterInt64.read(buf), + content=_UniffiConverterTypeContents.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterTypeRole.check_lower(value.role) + _UniffiConverterInt64.check_lower(value.created) + _UniffiConverterTypeContents.check_lower(value.content) + + @staticmethod + def write(value, buf): + _UniffiConverterTypeRole.write(value.role, buf) + _UniffiConverterInt64.write(value.created, buf) + _UniffiConverterTypeContents.write(value.content, buf) + + +class ModelConfig: + """ + Configuration for model-specific settings and limits + """ + + model_name: "str" + """ + The name of the model to use + """ + + context_limit: "typing.Optional[int]" + """ + Optional explicit context limit that overrides any defaults + """ + + temperature: "typing.Optional[float]" + """ + Optional temperature setting (0.0 - 1.0) + """ + + max_tokens: "typing.Optional[int]" + """ + Optional maximum tokens to generate + """ + + def __init__(self, *, model_name: "str", context_limit: "typing.Optional[int]", temperature: "typing.Optional[float]", max_tokens: "typing.Optional[int]"): + self.model_name = model_name + self.context_limit = context_limit + self.temperature = temperature + self.max_tokens = max_tokens + + def __str__(self): + return "ModelConfig(model_name={}, context_limit={}, temperature={}, max_tokens={})".format(self.model_name, self.context_limit, self.temperature, self.max_tokens) + + def __eq__(self, other): + if self.model_name != other.model_name: + return False + if self.context_limit != other.context_limit: + return False + if self.temperature != other.temperature: + return False + if self.max_tokens != other.max_tokens: + return False + return True + +class _UniffiConverterTypeModelConfig(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return ModelConfig( + model_name=_UniffiConverterString.read(buf), + context_limit=_UniffiConverterOptionalUInt32.read(buf), + temperature=_UniffiConverterOptionalFloat.read(buf), + max_tokens=_UniffiConverterOptionalInt32.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.model_name) + _UniffiConverterOptionalUInt32.check_lower(value.context_limit) + _UniffiConverterOptionalFloat.check_lower(value.temperature) + _UniffiConverterOptionalInt32.check_lower(value.max_tokens) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.model_name, buf) + _UniffiConverterOptionalUInt32.write(value.context_limit, buf) + _UniffiConverterOptionalFloat.write(value.temperature, buf) + _UniffiConverterOptionalInt32.write(value.max_tokens, buf) + + +class ProviderCompleteResponse: + message: "Message" + model: "str" + usage: "Usage" + def __init__(self, *, message: "Message", model: "str", usage: "Usage"): + self.message = message + self.model = model + self.usage = usage + + def __str__(self): + return "ProviderCompleteResponse(message={}, model={}, usage={})".format(self.message, self.model, self.usage) + + def __eq__(self, other): + if self.message != other.message: + return False + if self.model != other.model: + return False + if self.usage != other.usage: + return False + return True + +class _UniffiConverterTypeProviderCompleteResponse(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return ProviderCompleteResponse( + message=_UniffiConverterTypeMessage.read(buf), + model=_UniffiConverterString.read(buf), + usage=_UniffiConverterTypeUsage.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterTypeMessage.check_lower(value.message) + _UniffiConverterString.check_lower(value.model) + _UniffiConverterTypeUsage.check_lower(value.usage) + + @staticmethod + def write(value, buf): + _UniffiConverterTypeMessage.write(value.message, buf) + _UniffiConverterString.write(value.model, buf) + _UniffiConverterTypeUsage.write(value.usage, buf) + + +class RedactedThinkingContent: + data: "str" + def __init__(self, *, data: "str"): + self.data = data + + def __str__(self): + return "RedactedThinkingContent(data={})".format(self.data) + + def __eq__(self, other): + if self.data != other.data: + return False + return True + +class _UniffiConverterTypeRedactedThinkingContent(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return RedactedThinkingContent( + data=_UniffiConverterString.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.data) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.data, buf) + + +class RuntimeMetrics: + total_time_sec: "float" + total_time_sec_provider: "float" + tokens_per_second: "typing.Optional[float]" + def __init__(self, *, total_time_sec: "float", total_time_sec_provider: "float", tokens_per_second: "typing.Optional[float]"): + self.total_time_sec = total_time_sec + self.total_time_sec_provider = total_time_sec_provider + self.tokens_per_second = tokens_per_second + + def __str__(self): + return "RuntimeMetrics(total_time_sec={}, total_time_sec_provider={}, tokens_per_second={})".format(self.total_time_sec, self.total_time_sec_provider, self.tokens_per_second) + + def __eq__(self, other): + if self.total_time_sec != other.total_time_sec: + return False + if self.total_time_sec_provider != other.total_time_sec_provider: + return False + if self.tokens_per_second != other.tokens_per_second: + return False + return True + +class _UniffiConverterTypeRuntimeMetrics(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return RuntimeMetrics( + total_time_sec=_UniffiConverterFloat.read(buf), + total_time_sec_provider=_UniffiConverterFloat.read(buf), + tokens_per_second=_UniffiConverterOptionalDouble.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterFloat.check_lower(value.total_time_sec) + _UniffiConverterFloat.check_lower(value.total_time_sec_provider) + _UniffiConverterOptionalDouble.check_lower(value.tokens_per_second) + + @staticmethod + def write(value, buf): + _UniffiConverterFloat.write(value.total_time_sec, buf) + _UniffiConverterFloat.write(value.total_time_sec_provider, buf) + _UniffiConverterOptionalDouble.write(value.tokens_per_second, buf) + + +class TextContent: + text: "str" + def __init__(self, *, text: "str"): + self.text = text + + def __str__(self): + return "TextContent(text={})".format(self.text) + + def __eq__(self, other): + if self.text != other.text: + return False + return True + +class _UniffiConverterTypeTextContent(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return TextContent( + text=_UniffiConverterString.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.text) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.text, buf) + + +class ThinkingContent: + thinking: "str" + signature: "str" + def __init__(self, *, thinking: "str", signature: "str"): + self.thinking = thinking + self.signature = signature + + def __str__(self): + return "ThinkingContent(thinking={}, signature={})".format(self.thinking, self.signature) + + def __eq__(self, other): + if self.thinking != other.thinking: + return False + if self.signature != other.signature: + return False + return True + +class _UniffiConverterTypeThinkingContent(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return ThinkingContent( + thinking=_UniffiConverterString.read(buf), + signature=_UniffiConverterString.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.thinking) + _UniffiConverterString.check_lower(value.signature) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.thinking, buf) + _UniffiConverterString.write(value.signature, buf) + + +class ToolRequest: + id: "str" + tool_call: "ToolRequestToolCall" + def __init__(self, *, id: "str", tool_call: "ToolRequestToolCall"): + self.id = id + self.tool_call = tool_call + + def __str__(self): + return "ToolRequest(id={}, tool_call={})".format(self.id, self.tool_call) + + def __eq__(self, other): + if self.id != other.id: + return False + if self.tool_call != other.tool_call: + return False + return True + +class _UniffiConverterTypeToolRequest(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return ToolRequest( + id=_UniffiConverterString.read(buf), + tool_call=_UniffiConverterTypeToolRequestToolCall.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.id) + _UniffiConverterTypeToolRequestToolCall.check_lower(value.tool_call) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.id, buf) + _UniffiConverterTypeToolRequestToolCall.write(value.tool_call, buf) + + +class ToolResponse: + id: "str" + tool_result: "ToolResponseToolResult" + def __init__(self, *, id: "str", tool_result: "ToolResponseToolResult"): + self.id = id + self.tool_result = tool_result + + def __str__(self): + return "ToolResponse(id={}, tool_result={})".format(self.id, self.tool_result) + + def __eq__(self, other): + if self.id != other.id: + return False + if self.tool_result != other.tool_result: + return False + return True + +class _UniffiConverterTypeToolResponse(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return ToolResponse( + id=_UniffiConverterString.read(buf), + tool_result=_UniffiConverterTypeToolResponseToolResult.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterString.check_lower(value.id) + _UniffiConverterTypeToolResponseToolResult.check_lower(value.tool_result) + + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value.id, buf) + _UniffiConverterTypeToolResponseToolResult.write(value.tool_result, buf) + + +class Usage: + input_tokens: "typing.Optional[int]" + output_tokens: "typing.Optional[int]" + total_tokens: "typing.Optional[int]" + def __init__(self, *, input_tokens: "typing.Optional[int]", output_tokens: "typing.Optional[int]", total_tokens: "typing.Optional[int]"): + self.input_tokens = input_tokens + self.output_tokens = output_tokens + self.total_tokens = total_tokens + + def __str__(self): + return "Usage(input_tokens={}, output_tokens={}, total_tokens={})".format(self.input_tokens, self.output_tokens, self.total_tokens) + + def __eq__(self, other): + if self.input_tokens != other.input_tokens: + return False + if self.output_tokens != other.output_tokens: + return False + if self.total_tokens != other.total_tokens: + return False + return True + +class _UniffiConverterTypeUsage(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + return Usage( + input_tokens=_UniffiConverterOptionalInt32.read(buf), + output_tokens=_UniffiConverterOptionalInt32.read(buf), + total_tokens=_UniffiConverterOptionalInt32.read(buf), + ) + + @staticmethod + def check_lower(value): + _UniffiConverterOptionalInt32.check_lower(value.input_tokens) + _UniffiConverterOptionalInt32.check_lower(value.output_tokens) + _UniffiConverterOptionalInt32.check_lower(value.total_tokens) + + @staticmethod + def write(value, buf): + _UniffiConverterOptionalInt32.write(value.input_tokens, buf) + _UniffiConverterOptionalInt32.write(value.output_tokens, buf) + _UniffiConverterOptionalInt32.write(value.total_tokens, buf) + + +# CompletionError +# We want to define each variant as a nested class that's also a subclass, +# which is tricky in Python. To accomplish this we're going to create each +# class separately, then manually add the child classes to the base class's +# __dict__. All of this happens in dummy class to avoid polluting the module +# namespace. +class CompletionError(Exception): + pass + +_UniffiTempCompletionError = CompletionError + +class CompletionError: # type: ignore + class UnknownProvider(_UniffiTempCompletionError): + + def __repr__(self): + return "CompletionError.UnknownProvider({})".format(repr(str(self))) + _UniffiTempCompletionError.UnknownProvider = UnknownProvider # type: ignore + class Provider(_UniffiTempCompletionError): + + def __repr__(self): + return "CompletionError.Provider({})".format(repr(str(self))) + _UniffiTempCompletionError.Provider = Provider # type: ignore + class Template(_UniffiTempCompletionError): + + def __repr__(self): + return "CompletionError.Template({})".format(repr(str(self))) + _UniffiTempCompletionError.Template = Template # type: ignore + class Json(_UniffiTempCompletionError): + + def __repr__(self): + return "CompletionError.Json({})".format(repr(str(self))) + _UniffiTempCompletionError.Json = Json # type: ignore + class ToolNotFound(_UniffiTempCompletionError): + + def __repr__(self): + return "CompletionError.ToolNotFound({})".format(repr(str(self))) + _UniffiTempCompletionError.ToolNotFound = ToolNotFound # type: ignore + +CompletionError = _UniffiTempCompletionError # type: ignore +del _UniffiTempCompletionError + + +class _UniffiConverterTypeCompletionError(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return CompletionError.UnknownProvider( + _UniffiConverterString.read(buf), + ) + if variant == 2: + return CompletionError.Provider( + _UniffiConverterString.read(buf), + ) + if variant == 3: + return CompletionError.Template( + _UniffiConverterString.read(buf), + ) + if variant == 4: + return CompletionError.Json( + _UniffiConverterString.read(buf), + ) + if variant == 5: + return CompletionError.ToolNotFound( + _UniffiConverterString.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if isinstance(value, CompletionError.UnknownProvider): + return + if isinstance(value, CompletionError.Provider): + return + if isinstance(value, CompletionError.Template): + return + if isinstance(value, CompletionError.Json): + return + if isinstance(value, CompletionError.ToolNotFound): + return + + @staticmethod + def write(value, buf): + if isinstance(value, CompletionError.UnknownProvider): + buf.write_i32(1) + if isinstance(value, CompletionError.Provider): + buf.write_i32(2) + if isinstance(value, CompletionError.Template): + buf.write_i32(3) + if isinstance(value, CompletionError.Json): + buf.write_i32(4) + if isinstance(value, CompletionError.ToolNotFound): + buf.write_i32(5) + + + + + +class Content: + def __init__(self): + raise RuntimeError("Content cannot be instantiated directly") + + # Each enum variant is a nested class of the enum itself. + class TEXT: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"Content.TEXT{self._values!r}" + + def __eq__(self, other): + if not other.is_TEXT(): + return False + return self._values == other._values + class IMAGE: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"Content.IMAGE{self._values!r}" + + def __eq__(self, other): + if not other.is_IMAGE(): + return False + return self._values == other._values + + + # For each variant, we have `is_NAME` and `is_name` methods for easily checking + # whether an instance is that variant. + def is_TEXT(self) -> bool: + return isinstance(self, Content.TEXT) + def is_text(self) -> bool: + return isinstance(self, Content.TEXT) + def is_IMAGE(self) -> bool: + return isinstance(self, Content.IMAGE) + def is_image(self) -> bool: + return isinstance(self, Content.IMAGE) + + +# Now, a little trick - we make each nested variant class be a subclass of the main +# enum class, so that method calls and instance checks etc will work intuitively. +# We might be able to do this a little more neatly with a metaclass, but this'll do. +Content.TEXT = type("Content.TEXT", (Content.TEXT, Content,), {}) # type: ignore +Content.IMAGE = type("Content.IMAGE", (Content.IMAGE, Content,), {}) # type: ignore + + + + +class _UniffiConverterTypeContent(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return Content.TEXT( + _UniffiConverterTypeTextContent.read(buf), + ) + if variant == 2: + return Content.IMAGE( + _UniffiConverterTypeImageContent.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value.is_TEXT(): + _UniffiConverterTypeTextContent.check_lower(value._values[0]) + return + if value.is_IMAGE(): + _UniffiConverterTypeImageContent.check_lower(value._values[0]) + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value.is_TEXT(): + buf.write_i32(1) + _UniffiConverterTypeTextContent.write(value._values[0], buf) + if value.is_IMAGE(): + buf.write_i32(2) + _UniffiConverterTypeImageContent.write(value._values[0], buf) + + + + + + + +class MessageContent: + """ + Content passed inside a message, which can be both simple content and tool content + """ + + def __init__(self): + raise RuntimeError("MessageContent cannot be instantiated directly") + + # Each enum variant is a nested class of the enum itself. + class TEXT: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"MessageContent.TEXT{self._values!r}" + + def __eq__(self, other): + if not other.is_TEXT(): + return False + return self._values == other._values + class IMAGE: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"MessageContent.IMAGE{self._values!r}" + + def __eq__(self, other): + if not other.is_IMAGE(): + return False + return self._values == other._values + class TOOL_REQ: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"MessageContent.TOOL_REQ{self._values!r}" + + def __eq__(self, other): + if not other.is_TOOL_REQ(): + return False + return self._values == other._values + class TOOL_RESP: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"MessageContent.TOOL_RESP{self._values!r}" + + def __eq__(self, other): + if not other.is_TOOL_RESP(): + return False + return self._values == other._values + class THINKING: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"MessageContent.THINKING{self._values!r}" + + def __eq__(self, other): + if not other.is_THINKING(): + return False + return self._values == other._values + class REDACTED_THINKING: + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __str__(self): + return f"MessageContent.REDACTED_THINKING{self._values!r}" + + def __eq__(self, other): + if not other.is_REDACTED_THINKING(): + return False + return self._values == other._values + + + # For each variant, we have `is_NAME` and `is_name` methods for easily checking + # whether an instance is that variant. + def is_TEXT(self) -> bool: + return isinstance(self, MessageContent.TEXT) + def is_text(self) -> bool: + return isinstance(self, MessageContent.TEXT) + def is_IMAGE(self) -> bool: + return isinstance(self, MessageContent.IMAGE) + def is_image(self) -> bool: + return isinstance(self, MessageContent.IMAGE) + def is_TOOL_REQ(self) -> bool: + return isinstance(self, MessageContent.TOOL_REQ) + def is_tool_req(self) -> bool: + return isinstance(self, MessageContent.TOOL_REQ) + def is_TOOL_RESP(self) -> bool: + return isinstance(self, MessageContent.TOOL_RESP) + def is_tool_resp(self) -> bool: + return isinstance(self, MessageContent.TOOL_RESP) + def is_THINKING(self) -> bool: + return isinstance(self, MessageContent.THINKING) + def is_thinking(self) -> bool: + return isinstance(self, MessageContent.THINKING) + def is_REDACTED_THINKING(self) -> bool: + return isinstance(self, MessageContent.REDACTED_THINKING) + def is_redacted_thinking(self) -> bool: + return isinstance(self, MessageContent.REDACTED_THINKING) + + +# Now, a little trick - we make each nested variant class be a subclass of the main +# enum class, so that method calls and instance checks etc will work intuitively. +# We might be able to do this a little more neatly with a metaclass, but this'll do. +MessageContent.TEXT = type("MessageContent.TEXT", (MessageContent.TEXT, MessageContent,), {}) # type: ignore +MessageContent.IMAGE = type("MessageContent.IMAGE", (MessageContent.IMAGE, MessageContent,), {}) # type: ignore +MessageContent.TOOL_REQ = type("MessageContent.TOOL_REQ", (MessageContent.TOOL_REQ, MessageContent,), {}) # type: ignore +MessageContent.TOOL_RESP = type("MessageContent.TOOL_RESP", (MessageContent.TOOL_RESP, MessageContent,), {}) # type: ignore +MessageContent.THINKING = type("MessageContent.THINKING", (MessageContent.THINKING, MessageContent,), {}) # type: ignore +MessageContent.REDACTED_THINKING = type("MessageContent.REDACTED_THINKING", (MessageContent.REDACTED_THINKING, MessageContent,), {}) # type: ignore + + + + +class _UniffiConverterTypeMessageContent(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return MessageContent.TEXT( + _UniffiConverterTypeTextContent.read(buf), + ) + if variant == 2: + return MessageContent.IMAGE( + _UniffiConverterTypeImageContent.read(buf), + ) + if variant == 3: + return MessageContent.TOOL_REQ( + _UniffiConverterTypeToolRequest.read(buf), + ) + if variant == 4: + return MessageContent.TOOL_RESP( + _UniffiConverterTypeToolResponse.read(buf), + ) + if variant == 5: + return MessageContent.THINKING( + _UniffiConverterTypeThinkingContent.read(buf), + ) + if variant == 6: + return MessageContent.REDACTED_THINKING( + _UniffiConverterTypeRedactedThinkingContent.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value.is_TEXT(): + _UniffiConverterTypeTextContent.check_lower(value._values[0]) + return + if value.is_IMAGE(): + _UniffiConverterTypeImageContent.check_lower(value._values[0]) + return + if value.is_TOOL_REQ(): + _UniffiConverterTypeToolRequest.check_lower(value._values[0]) + return + if value.is_TOOL_RESP(): + _UniffiConverterTypeToolResponse.check_lower(value._values[0]) + return + if value.is_THINKING(): + _UniffiConverterTypeThinkingContent.check_lower(value._values[0]) + return + if value.is_REDACTED_THINKING(): + _UniffiConverterTypeRedactedThinkingContent.check_lower(value._values[0]) + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value.is_TEXT(): + buf.write_i32(1) + _UniffiConverterTypeTextContent.write(value._values[0], buf) + if value.is_IMAGE(): + buf.write_i32(2) + _UniffiConverterTypeImageContent.write(value._values[0], buf) + if value.is_TOOL_REQ(): + buf.write_i32(3) + _UniffiConverterTypeToolRequest.write(value._values[0], buf) + if value.is_TOOL_RESP(): + buf.write_i32(4) + _UniffiConverterTypeToolResponse.write(value._values[0], buf) + if value.is_THINKING(): + buf.write_i32(5) + _UniffiConverterTypeThinkingContent.write(value._values[0], buf) + if value.is_REDACTED_THINKING(): + buf.write_i32(6) + _UniffiConverterTypeRedactedThinkingContent.write(value._values[0], buf) + + + + +# ProviderError +# We want to define each variant as a nested class that's also a subclass, +# which is tricky in Python. To accomplish this we're going to create each +# class separately, then manually add the child classes to the base class's +# __dict__. All of this happens in dummy class to avoid polluting the module +# namespace. +class ProviderError(Exception): + pass + +_UniffiTempProviderError = ProviderError + +class ProviderError: # type: ignore + class Authentication(_UniffiTempProviderError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ProviderError.Authentication({})".format(str(self)) + _UniffiTempProviderError.Authentication = Authentication # type: ignore + class ContextLengthExceeded(_UniffiTempProviderError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ProviderError.ContextLengthExceeded({})".format(str(self)) + _UniffiTempProviderError.ContextLengthExceeded = ContextLengthExceeded # type: ignore + class RateLimitExceeded(_UniffiTempProviderError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ProviderError.RateLimitExceeded({})".format(str(self)) + _UniffiTempProviderError.RateLimitExceeded = RateLimitExceeded # type: ignore + class ServerError(_UniffiTempProviderError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ProviderError.ServerError({})".format(str(self)) + _UniffiTempProviderError.ServerError = ServerError # type: ignore + class RequestFailed(_UniffiTempProviderError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ProviderError.RequestFailed({})".format(str(self)) + _UniffiTempProviderError.RequestFailed = RequestFailed # type: ignore + class ExecutionError(_UniffiTempProviderError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ProviderError.ExecutionError({})".format(str(self)) + _UniffiTempProviderError.ExecutionError = ExecutionError # type: ignore + class UsageError(_UniffiTempProviderError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ProviderError.UsageError({})".format(str(self)) + _UniffiTempProviderError.UsageError = UsageError # type: ignore + class ResponseParseError(_UniffiTempProviderError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ProviderError.ResponseParseError({})".format(str(self)) + _UniffiTempProviderError.ResponseParseError = ResponseParseError # type: ignore + +ProviderError = _UniffiTempProviderError # type: ignore +del _UniffiTempProviderError + + +class _UniffiConverterTypeProviderError(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return ProviderError.Authentication( + _UniffiConverterString.read(buf), + ) + if variant == 2: + return ProviderError.ContextLengthExceeded( + _UniffiConverterString.read(buf), + ) + if variant == 3: + return ProviderError.RateLimitExceeded( + _UniffiConverterString.read(buf), + ) + if variant == 4: + return ProviderError.ServerError( + _UniffiConverterString.read(buf), + ) + if variant == 5: + return ProviderError.RequestFailed( + _UniffiConverterString.read(buf), + ) + if variant == 6: + return ProviderError.ExecutionError( + _UniffiConverterString.read(buf), + ) + if variant == 7: + return ProviderError.UsageError( + _UniffiConverterString.read(buf), + ) + if variant == 8: + return ProviderError.ResponseParseError( + _UniffiConverterString.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if isinstance(value, ProviderError.Authentication): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ProviderError.ContextLengthExceeded): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ProviderError.RateLimitExceeded): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ProviderError.ServerError): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ProviderError.RequestFailed): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ProviderError.ExecutionError): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ProviderError.UsageError): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ProviderError.ResponseParseError): + _UniffiConverterString.check_lower(value._values[0]) + return + + @staticmethod + def write(value, buf): + if isinstance(value, ProviderError.Authentication): + buf.write_i32(1) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ProviderError.ContextLengthExceeded): + buf.write_i32(2) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ProviderError.RateLimitExceeded): + buf.write_i32(3) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ProviderError.ServerError): + buf.write_i32(4) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ProviderError.RequestFailed): + buf.write_i32(5) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ProviderError.ExecutionError): + buf.write_i32(6) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ProviderError.UsageError): + buf.write_i32(7) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ProviderError.ResponseParseError): + buf.write_i32(8) + _UniffiConverterString.write(value._values[0], buf) + + + + + +class Role(enum.Enum): + USER = 0 + + ASSISTANT = 1 + + + +class _UniffiConverterTypeRole(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return Role.USER + if variant == 2: + return Role.ASSISTANT + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value == Role.USER: + return + if value == Role.ASSISTANT: + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value == Role.USER: + buf.write_i32(1) + if value == Role.ASSISTANT: + buf.write_i32(2) + + + + + + + +class ToolApprovalMode(enum.Enum): + AUTO = 0 + + MANUAL = 1 + + SMART = 2 + + + +class _UniffiConverterTypeToolApprovalMode(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return ToolApprovalMode.AUTO + if variant == 2: + return ToolApprovalMode.MANUAL + if variant == 3: + return ToolApprovalMode.SMART + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if value == ToolApprovalMode.AUTO: + return + if value == ToolApprovalMode.MANUAL: + return + if value == ToolApprovalMode.SMART: + return + raise ValueError(value) + + @staticmethod + def write(value, buf): + if value == ToolApprovalMode.AUTO: + buf.write_i32(1) + if value == ToolApprovalMode.MANUAL: + buf.write_i32(2) + if value == ToolApprovalMode.SMART: + buf.write_i32(3) + + + + +# ToolError +# We want to define each variant as a nested class that's also a subclass, +# which is tricky in Python. To accomplish this we're going to create each +# class separately, then manually add the child classes to the base class's +# __dict__. All of this happens in dummy class to avoid polluting the module +# namespace. +class ToolError(Exception): + pass + +_UniffiTempToolError = ToolError + +class ToolError: # type: ignore + class InvalidParameters(_UniffiTempToolError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ToolError.InvalidParameters({})".format(str(self)) + _UniffiTempToolError.InvalidParameters = InvalidParameters # type: ignore + class ExecutionError(_UniffiTempToolError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ToolError.ExecutionError({})".format(str(self)) + _UniffiTempToolError.ExecutionError = ExecutionError # type: ignore + class SchemaError(_UniffiTempToolError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ToolError.SchemaError({})".format(str(self)) + _UniffiTempToolError.SchemaError = SchemaError # type: ignore + class NotFound(_UniffiTempToolError): + def __init__(self, *values): + if len(values) != 1: + raise TypeError(f"Expected 1 arguments, found {len(values)}") + if not isinstance(values[0], str): + raise TypeError(f"unexpected type for tuple element 0 - expected 'str', got '{type(values[0])}'") + super().__init__(", ".join(map(repr, values))) + self._values = values + + def __getitem__(self, index): + return self._values[index] + + def __repr__(self): + return "ToolError.NotFound({})".format(str(self)) + _UniffiTempToolError.NotFound = NotFound # type: ignore + +ToolError = _UniffiTempToolError # type: ignore +del _UniffiTempToolError + + +class _UniffiConverterTypeToolError(_UniffiConverterRustBuffer): + @staticmethod + def read(buf): + variant = buf.read_i32() + if variant == 1: + return ToolError.InvalidParameters( + _UniffiConverterString.read(buf), + ) + if variant == 2: + return ToolError.ExecutionError( + _UniffiConverterString.read(buf), + ) + if variant == 3: + return ToolError.SchemaError( + _UniffiConverterString.read(buf), + ) + if variant == 4: + return ToolError.NotFound( + _UniffiConverterString.read(buf), + ) + raise InternalError("Raw enum value doesn't match any cases") + + @staticmethod + def check_lower(value): + if isinstance(value, ToolError.InvalidParameters): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ToolError.ExecutionError): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ToolError.SchemaError): + _UniffiConverterString.check_lower(value._values[0]) + return + if isinstance(value, ToolError.NotFound): + _UniffiConverterString.check_lower(value._values[0]) + return + + @staticmethod + def write(value, buf): + if isinstance(value, ToolError.InvalidParameters): + buf.write_i32(1) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ToolError.ExecutionError): + buf.write_i32(2) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ToolError.SchemaError): + buf.write_i32(3) + _UniffiConverterString.write(value._values[0], buf) + if isinstance(value, ToolError.NotFound): + buf.write_i32(4) + _UniffiConverterString.write(value._values[0], buf) + + + +class _UniffiConverterOptionalUInt32(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + if value is not None: + _UniffiConverterUInt32.check_lower(value) + + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + _UniffiConverterUInt32.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return _UniffiConverterUInt32.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + + + +class _UniffiConverterOptionalInt32(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + if value is not None: + _UniffiConverterInt32.check_lower(value) + + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + _UniffiConverterInt32.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return _UniffiConverterInt32.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + + + +class _UniffiConverterOptionalFloat(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + if value is not None: + _UniffiConverterFloat.check_lower(value) + + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + _UniffiConverterFloat.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return _UniffiConverterFloat.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + + + +class _UniffiConverterOptionalDouble(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + if value is not None: + _UniffiConverterDouble.check_lower(value) + + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + _UniffiConverterDouble.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return _UniffiConverterDouble.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + + + +class _UniffiConverterOptionalString(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + if value is not None: + _UniffiConverterString.check_lower(value) + + @classmethod + def write(cls, value, buf): + if value is None: + buf.write_u8(0) + return + + buf.write_u8(1) + _UniffiConverterString.write(value, buf) + + @classmethod + def read(cls, buf): + flag = buf.read_u8() + if flag == 0: + return None + elif flag == 1: + return _UniffiConverterString.read(buf) + else: + raise InternalError("Unexpected flag byte for optional type") + + + +class _UniffiConverterSequenceTypeExtensionConfig(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + for item in value: + _UniffiConverterTypeExtensionConfig.check_lower(item) + + @classmethod + def write(cls, value, buf): + items = len(value) + buf.write_i32(items) + for item in value: + _UniffiConverterTypeExtensionConfig.write(item, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative sequence length") + + return [ + _UniffiConverterTypeExtensionConfig.read(buf) for i in range(count) + ] + + + +class _UniffiConverterSequenceTypeMessage(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + for item in value: + _UniffiConverterTypeMessage.check_lower(item) + + @classmethod + def write(cls, value, buf): + items = len(value) + buf.write_i32(items) + for item in value: + _UniffiConverterTypeMessage.write(item, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative sequence length") + + return [ + _UniffiConverterTypeMessage.read(buf) for i in range(count) + ] + + + +class _UniffiConverterSequenceTypeMessageContent(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + for item in value: + _UniffiConverterTypeMessageContent.check_lower(item) + + @classmethod + def write(cls, value, buf): + items = len(value) + buf.write_i32(items) + for item in value: + _UniffiConverterTypeMessageContent.write(item, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative sequence length") + + return [ + _UniffiConverterTypeMessageContent.read(buf) for i in range(count) + ] + + + +class _UniffiConverterSequenceTypeToolConfig(_UniffiConverterRustBuffer): + @classmethod + def check_lower(cls, value): + for item in value: + _UniffiConverterTypeToolConfig.check_lower(item) + + @classmethod + def write(cls, value, buf): + items = len(value) + buf.write_i32(items) + for item in value: + _UniffiConverterTypeToolConfig.write(item, buf) + + @classmethod + def read(cls, buf): + count = buf.read_i32() + if count < 0: + raise InternalError("Unexpected negative sequence length") + + return [ + _UniffiConverterTypeToolConfig.read(buf) for i in range(count) + ] + + +class _UniffiConverterTypeCompletionRequest: + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value, buf) + + @staticmethod + def read(buf): + return _UniffiConverterString.read(buf) + + @staticmethod + def lift(value): + return _UniffiConverterString.lift(value) + + @staticmethod + def check_lower(value): + return _UniffiConverterString.check_lower(value) + + @staticmethod + def lower(value): + return _UniffiConverterString.lower(value) + + +class _UniffiConverterTypeContents: + @staticmethod + def write(value, buf): + _UniffiConverterSequenceTypeMessageContent.write(value, buf) + + @staticmethod + def read(buf): + return _UniffiConverterSequenceTypeMessageContent.read(buf) + + @staticmethod + def lift(value): + return _UniffiConverterSequenceTypeMessageContent.lift(value) + + @staticmethod + def check_lower(value): + return _UniffiConverterSequenceTypeMessageContent.check_lower(value) + + @staticmethod + def lower(value): + return _UniffiConverterSequenceTypeMessageContent.lower(value) + + +class _UniffiConverterTypeJsonValueFfi: + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value, buf) + + @staticmethod + def read(buf): + return _UniffiConverterString.read(buf) + + @staticmethod + def lift(value): + return _UniffiConverterString.lift(value) + + @staticmethod + def check_lower(value): + return _UniffiConverterString.check_lower(value) + + @staticmethod + def lower(value): + return _UniffiConverterString.lower(value) + + +class _UniffiConverterTypeToolConfig: + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value, buf) + + @staticmethod + def read(buf): + return _UniffiConverterString.read(buf) + + @staticmethod + def lift(value): + return _UniffiConverterString.lift(value) + + @staticmethod + def check_lower(value): + return _UniffiConverterString.check_lower(value) + + @staticmethod + def lower(value): + return _UniffiConverterString.lower(value) + + +class _UniffiConverterTypeToolRequestToolCall: + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value, buf) + + @staticmethod + def read(buf): + return _UniffiConverterString.read(buf) + + @staticmethod + def lift(value): + return _UniffiConverterString.lift(value) + + @staticmethod + def check_lower(value): + return _UniffiConverterString.check_lower(value) + + @staticmethod + def lower(value): + return _UniffiConverterString.lower(value) + + +class _UniffiConverterTypeToolResponseToolResult: + @staticmethod + def write(value, buf): + _UniffiConverterString.write(value, buf) + + @staticmethod + def read(buf): + return _UniffiConverterString.read(buf) + + @staticmethod + def lift(value): + return _UniffiConverterString.lift(value) + + @staticmethod + def check_lower(value): + return _UniffiConverterString.check_lower(value) + + @staticmethod + def lower(value): + return _UniffiConverterString.lower(value) + +# objects. +CompletionRequest = str +Contents = typing.List[MessageContent] +JsonValueFfi = str +ToolConfig = str +ToolRequestToolCall = str +ToolResponseToolResult = str + +# Async support# RustFuturePoll values +_UNIFFI_RUST_FUTURE_POLL_READY = 0 +_UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1 + +# Stores futures for _uniffi_continuation_callback +_UniffiContinuationHandleMap = _UniffiHandleMap() + +_UNIFFI_GLOBAL_EVENT_LOOP = None + +""" +Set the event loop to use for async functions + +This is needed if some async functions run outside of the eventloop, for example: + - A non-eventloop thread is spawned, maybe from `EventLoop.run_in_executor` or maybe from the + Rust code spawning its own thread. + - The Rust code calls an async callback method from a sync callback function, using something + like `pollster` to block on the async call. + +In this case, we need an event loop to run the Python async function, but there's no eventloop set +for the thread. Use `uniffi_set_event_loop` to force an eventloop to be used in this case. +""" +def uniffi_set_event_loop(eventloop: asyncio.BaseEventLoop): + global _UNIFFI_GLOBAL_EVENT_LOOP + _UNIFFI_GLOBAL_EVENT_LOOP = eventloop + +def _uniffi_get_event_loop(): + if _UNIFFI_GLOBAL_EVENT_LOOP is not None: + return _UNIFFI_GLOBAL_EVENT_LOOP + else: + return asyncio.get_running_loop() + +# Continuation callback for async functions +# lift the return value or error and resolve the future, causing the async function to resume. +@_UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK +def _uniffi_continuation_callback(future_ptr, poll_code): + (eventloop, future) = _UniffiContinuationHandleMap.remove(future_ptr) + eventloop.call_soon_threadsafe(_uniffi_set_future_result, future, poll_code) + +def _uniffi_set_future_result(future, poll_code): + if not future.cancelled(): + future.set_result(poll_code) + +async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free, lift_func, error_ffi_converter): + try: + eventloop = _uniffi_get_event_loop() + + # Loop and poll until we see a _UNIFFI_RUST_FUTURE_POLL_READY value + while True: + future = eventloop.create_future() + ffi_poll( + rust_future, + _uniffi_continuation_callback, + _UniffiContinuationHandleMap.insert((eventloop, future)), + ) + poll_code = await future + if poll_code == _UNIFFI_RUST_FUTURE_POLL_READY: + break + + return lift_func( + _uniffi_rust_call_with_error(error_ffi_converter, ffi_complete, rust_future) + ) + finally: + ffi_free(rust_future) +async def completion(req: "CompletionRequest") -> "CompletionResponse": + + """ + Public API for the Goose LLM completion function + """ + + _UniffiConverterTypeCompletionRequest.check_lower(req) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_goose_llm_fn_func_completion( + _UniffiConverterTypeCompletionRequest.lower(req)), + _UniffiLib.ffi_goose_llm_rust_future_poll_rust_buffer, + _UniffiLib.ffi_goose_llm_rust_future_complete_rust_buffer, + _UniffiLib.ffi_goose_llm_rust_future_free_rust_buffer, + # lift function + _UniffiConverterTypeCompletionResponse.lift, + + # Error FFI converter +_UniffiConverterTypeCompletionError, + + ) + +def create_completion_request(provider_name: "str",provider_config: "JsonValueFfi",model_config: "ModelConfig",system_preamble: "str",messages: "typing.List[Message]",extensions: "typing.List[ExtensionConfig]") -> "CompletionRequest": + _UniffiConverterString.check_lower(provider_name) + + _UniffiConverterTypeJsonValueFfi.check_lower(provider_config) + + _UniffiConverterTypeModelConfig.check_lower(model_config) + + _UniffiConverterString.check_lower(system_preamble) + + _UniffiConverterSequenceTypeMessage.check_lower(messages) + + _UniffiConverterSequenceTypeExtensionConfig.check_lower(extensions) + + return _UniffiConverterTypeCompletionRequest.lift(_uniffi_rust_call(_UniffiLib.uniffi_goose_llm_fn_func_create_completion_request, + _UniffiConverterString.lower(provider_name), + _UniffiConverterTypeJsonValueFfi.lower(provider_config), + _UniffiConverterTypeModelConfig.lower(model_config), + _UniffiConverterString.lower(system_preamble), + _UniffiConverterSequenceTypeMessage.lower(messages), + _UniffiConverterSequenceTypeExtensionConfig.lower(extensions))) + + +def create_tool_config(name: "str",description: "str",input_schema: "JsonValueFfi",approval_mode: "ToolApprovalMode") -> "ToolConfig": + _UniffiConverterString.check_lower(name) + + _UniffiConverterString.check_lower(description) + + _UniffiConverterTypeJsonValueFfi.check_lower(input_schema) + + _UniffiConverterTypeToolApprovalMode.check_lower(approval_mode) + + return _UniffiConverterTypeToolConfig.lift(_uniffi_rust_call(_UniffiLib.uniffi_goose_llm_fn_func_create_tool_config, + _UniffiConverterString.lower(name), + _UniffiConverterString.lower(description), + _UniffiConverterTypeJsonValueFfi.lower(input_schema), + _UniffiConverterTypeToolApprovalMode.lower(approval_mode))) + +async def generate_session_name(provider_name: "str",provider_config: "JsonValueFfi",messages: "typing.List[Message]") -> "str": + + """ + Generates a short (≤4 words) session name + """ + + _UniffiConverterString.check_lower(provider_name) + + _UniffiConverterTypeJsonValueFfi.check_lower(provider_config) + + _UniffiConverterSequenceTypeMessage.check_lower(messages) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_goose_llm_fn_func_generate_session_name( + _UniffiConverterString.lower(provider_name), + _UniffiConverterTypeJsonValueFfi.lower(provider_config), + _UniffiConverterSequenceTypeMessage.lower(messages)), + _UniffiLib.ffi_goose_llm_rust_future_poll_rust_buffer, + _UniffiLib.ffi_goose_llm_rust_future_complete_rust_buffer, + _UniffiLib.ffi_goose_llm_rust_future_free_rust_buffer, + # lift function + _UniffiConverterString.lift, + + # Error FFI converter +_UniffiConverterTypeProviderError, + + ) +async def generate_tooltip(provider_name: "str",provider_config: "JsonValueFfi",messages: "typing.List[Message]") -> "str": + + """ + Generates a tooltip summarizing the last two messages in the session, + including any tool calls or results. + """ + + _UniffiConverterString.check_lower(provider_name) + + _UniffiConverterTypeJsonValueFfi.check_lower(provider_config) + + _UniffiConverterSequenceTypeMessage.check_lower(messages) + + return await _uniffi_rust_call_async( + _UniffiLib.uniffi_goose_llm_fn_func_generate_tooltip( + _UniffiConverterString.lower(provider_name), + _UniffiConverterTypeJsonValueFfi.lower(provider_config), + _UniffiConverterSequenceTypeMessage.lower(messages)), + _UniffiLib.ffi_goose_llm_rust_future_poll_rust_buffer, + _UniffiLib.ffi_goose_llm_rust_future_complete_rust_buffer, + _UniffiLib.ffi_goose_llm_rust_future_free_rust_buffer, + # lift function + _UniffiConverterString.lift, + + # Error FFI converter +_UniffiConverterTypeProviderError, + + ) + +def print_messages(messages: "typing.List[Message]") -> None: + _UniffiConverterSequenceTypeMessage.check_lower(messages) + + _uniffi_rust_call(_UniffiLib.uniffi_goose_llm_fn_func_print_messages, + _UniffiConverterSequenceTypeMessage.lower(messages)) + + +__all__ = [ + "InternalError", + "CompletionError", + "Content", + "MessageContent", + "ProviderError", + "Role", + "ToolApprovalMode", + "ToolError", + "CompletionResponse", + "ExtensionConfig", + "ImageContent", + "Message", + "ModelConfig", + "ProviderCompleteResponse", + "RedactedThinkingContent", + "RuntimeMetrics", + "TextContent", + "ThinkingContent", + "ToolRequest", + "ToolResponse", + "Usage", + "completion", + "create_completion_request", + "create_tool_config", + "generate_session_name", + "generate_tooltip", + "print_messages", +] + diff --git a/bindings/python/usage.py b/bindings/python/usage.py new file mode 100644 index 0000000000..bdfb391812 --- /dev/null +++ b/bindings/python/usage.py @@ -0,0 +1,133 @@ +import asyncio +import os +import time +from goose_llm import ( + Message, MessageContent, TextContent, ToolRequest, ToolResponse, + Role, ModelConfig, ToolApprovalMode, + create_tool_config, ExtensionConfig, + generate_session_name, generate_tooltip, + create_completion_request, completion +) + +async def main(): + now = int(time.time()) + + # 1) User sends a plain-text prompt + messages = [ + Message( + role=Role.USER, + created=now, + content=[MessageContent.TEXT(TextContent(text="What is 7 x 6?"))] + ), + + # 2) Assistant makes a tool request + Message( + role=Role.ASSISTANT, + created=now + 2, + content=[MessageContent.TOOL_REQ(ToolRequest( + id="calc1", + tool_call=""" + { + "status": "success", + "value": { + "name": "calculator_extension__toolname", + "arguments": { + "operation": "multiply", + "numbers": [7, 6] + }, + "needsApproval": false + } + } + """ + ))] + ), + + # 3) User sends tool result + Message( + role=Role.USER, + created=now + 3, + content=[MessageContent.TOOL_RESP(ToolResponse( + id="calc1", + tool_result=""" + { + "status": "success", + "value": [ + {"type": "text", "text": "42"} + ] + } + """ + ))] + ) + ] + + provider_name = "databricks" + provider_config = f'''{{ + "host": "{os.environ.get("DATABRICKS_HOST")}", + "token": "{os.environ.get("DATABRICKS_TOKEN")}" + }}''' + + print(f"Provider Name: {provider_name}") + print(f"Provider Config: {provider_config}") + + session_name = await generate_session_name(provider_name, provider_config, messages) + print(f"\nSession Name: {session_name}") + + tooltip = await generate_tooltip(provider_name, provider_config, messages) + print(f"\nTooltip: {tooltip}") + + model_config = ModelConfig( + model_name="goose-gpt-4-1", + max_tokens=500, + temperature=0.1, + context_limit=4096, + ) + + calculator_tool = create_tool_config( + name="calculator", + description="Perform basic arithmetic operations", + input_schema=""" + { + "type": "object", + "required": ["operation", "numbers"], + "properties": { + "operation": { + "type": "string", + "enum": ["add", "subtract", "multiply", "divide"], + "description": "The arithmetic operation to perform" + }, + "numbers": { + "type": "array", + "items": { "type": "number" }, + "description": "List of numbers to operate on in order" + } + } + } + """, + approval_mode=ToolApprovalMode.AUTO + ) + + calculator_extension = ExtensionConfig( + name="calculator_extension", + instructions="This extension provides a calculator tool.", + tools=[calculator_tool] + ) + + system_preamble = "You are a helpful assistant." + extensions = [calculator_extension] + + req = create_completion_request( + provider_name, + provider_config, + model_config, + system_preamble, + messages, + extensions + ) + + resp = await completion(req) + print(f"\nCompletion Response:\n{resp.message}") + print(f"Msg content: {resp.message.content[0][0]}") + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/crates/goose-llm/README.md b/crates/goose-llm/README.md index dcf205c88a..5556d56ac7 100644 --- a/crates/goose-llm/README.md +++ b/crates/goose-llm/README.md @@ -31,26 +31,19 @@ Structure: │ └── goose_llm.kt ← auto-generated bindings ``` -Create Kotlin bindings: -``` +#### Create Kotlin bindings: + +```bash # run from project root directory cargo build -p goose-llm cargo run --features=uniffi/cli --bin uniffi-bindgen generate --library ./target/debug/libgoose_llm.dylib --language kotlin --out-dir bindings/kotlin ``` -Download jars in `bindings/kotlin/libs` directory (only need to do this once): -``` -pushd bindings/kotlin/libs/ -curl -O https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.9.0/kotlin-stdlib-1.9.0.jar -curl -O https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.3/kotlinx-coroutines-core-jvm-1.7.3.jar -curl -O https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.13.0/jna-5.13.0.jar -popd -``` +#### Kotlin -> Rust: run example -Compile & Run usage example from Kotlin -> Rust: -``` +```bash pushd bindings/kotlin/ kotlinc \ @@ -68,3 +61,19 @@ java \ popd ``` +You will have to download jars in `bindings/kotlin/libs` directory (only the first time): +```bash +pushd bindings/kotlin/libs/ +curl -O https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.9.0/kotlin-stdlib-1.9.0.jar +curl -O https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.3/kotlinx-coroutines-core-jvm-1.7.3.jar +curl -O https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.13.0/jna-5.13.0.jar +popd +``` + +#### Python -> Rust: generate bindings, run example + +```bash +cargo run --features=uniffi/cli --bin uniffi-bindgen generate --library ./target/debug/libgoose_llm.dylib --language python --out-dir bindings/python + +DYLD_LIBRARY_PATH=./target/debug python bindings/python/usage.py +``` diff --git a/crates/goose-llm/examples/simple.rs b/crates/goose-llm/examples/simple.rs index 0760a98066..9544f7ce20 100644 --- a/crates/goose-llm/examples/simple.rs +++ b/crates/goose-llm/examples/simple.rs @@ -3,6 +3,7 @@ use std::vec; use anyhow::Result; use goose_llm::{ completion, + extractors::generate_tooltip, types::completion::{ CompletionRequest, CompletionResponse, ExtensionConfig, ToolApprovalMode, ToolConfig, }, @@ -13,8 +14,12 @@ use serde_json::json; #[tokio::main] async fn main() -> Result<()> { let provider = "databricks"; - // let model_name = "goose-claude-3-5-sonnet"; // sequential tool calls - let model_name = "goose-gpt-4-1"; // parallel tool calls + let provider_config = json!({ + "host": std::env::var("DATABRICKS_HOST").expect("Missing DATABRICKS_HOST"), + "token": std::env::var("DATABRICKS_TOKEN").expect("Missing DATABRICKS_TOKEN"), + }); + // let model_name = "goose-gpt-4-1"; // parallel tool calls + let model_name = "claude-3-5-haiku"; let model_config = ModelConfig::new(model_name.to_string()); let calculator_tool = ToolConfig::new( @@ -92,18 +97,26 @@ async fn main() -> Result<()> { ] { println!("\n---------------\n"); println!("User Input: {text}"); - let messages = vec![Message::user().with_text(text)]; - let completion_response: CompletionResponse = completion(CompletionRequest { - provider_name: provider.to_string(), - model_config: model_config.clone(), - system_preamble: system_preamble.to_string(), - messages: messages, - extensions: extensions.clone(), - }) + let messages = vec![ + Message::user().with_text("Hi there!"), + Message::assistant().with_text("How can I help?"), + Message::user().with_text(text), + ]; + let completion_response: CompletionResponse = completion(CompletionRequest::new( + provider.to_string(), + provider_config.clone(), + model_config.clone(), + system_preamble.to_string(), + messages.clone(), + extensions.clone(), + )) .await?; // Print the response println!("\nCompletion Response:"); println!("{}", serde_json::to_string_pretty(&completion_response)?); + + let tooltip = generate_tooltip(provider, provider_config.clone().into(), &messages).await?; + println!("\nTooltip: {}", tooltip); } Ok(()) diff --git a/crates/goose-llm/src/completion.rs b/crates/goose-llm/src/completion.rs index e4936b7007..2095df7347 100644 --- a/crates/goose-llm/src/completion.rs +++ b/crates/goose-llm/src/completion.rs @@ -29,8 +29,12 @@ pub fn print_messages(messages: Vec) { pub async fn completion(req: CompletionRequest) -> Result { let start_total = Instant::now(); - let provider = create(&req.provider_name, req.model_config) - .map_err(|_| CompletionError::UnknownProvider(req.provider_name.to_string()))?; + let provider = create( + &req.provider_name, + req.provider_config.clone(), + req.model_config.clone(), + ) + .map_err(|_| CompletionError::UnknownProvider(req.provider_name.to_string()))?; let system_prompt = construct_system_prompt(&req.system_preamble, &req.extensions)?; let tools = collect_prefixed_tools(&req.extensions); diff --git a/crates/goose-llm/src/extractors/session_name.rs b/crates/goose-llm/src/extractors/session_name.rs index 6aeccd60da..2ba53605a9 100644 --- a/crates/goose-llm/src/extractors/session_name.rs +++ b/crates/goose-llm/src/extractors/session_name.rs @@ -1,9 +1,8 @@ -use crate::message::Message; use crate::model::ModelConfig; -use crate::providers::base::Provider; -use crate::providers::databricks::DatabricksProvider; +use crate::providers::create; use crate::providers::errors::ProviderError; use crate::types::core::Role; +use crate::{message::Message, types::json_value_ffi::JsonValueFfi}; use anyhow::Result; use indoc::indoc; use serde_json::{json, Value}; @@ -50,7 +49,20 @@ fn build_system_prompt() -> String { /// Generates a short (≤4 words) session name #[uniffi::export(async_runtime = "tokio")] -pub async fn generate_session_name(messages: &[Message]) -> Result { +pub async fn generate_session_name( + provider_name: &str, + provider_config: JsonValueFfi, + messages: &[Message], +) -> Result { + // Use OpenAI models specifically for this task + let model_name = if provider_name == "databricks" { + "goose-gpt-4-1" + } else { + "gpt-4.1" + }; + let model_cfg = ModelConfig::new(model_name.to_string()).with_temperature(Some(0.0)); + let provider = create(provider_name, provider_config.into(), model_cfg)?; + // Collect up to the first 3 user messages (truncated to 300 chars each) let context: Vec = messages .iter() @@ -75,10 +87,6 @@ pub async fn generate_session_name(messages: &[Message]) -> Result String { /// Generates a tooltip summarizing the last two messages in the session, /// including any tool calls or results. #[uniffi::export(async_runtime = "tokio")] -pub async fn generate_tooltip(messages: &[Message]) -> Result { +pub async fn generate_tooltip( + provider_name: &str, + provider_config: JsonValueFfi, + messages: &[Message], +) -> Result { + // Use OpenAI models specifically for this task + let model_name = if provider_name == "databricks" { + "goose-gpt-4-1" + } else { + "gpt-4.1" + }; + let model_cfg = ModelConfig::new(model_name.to_string()).with_temperature(Some(0.0)); + let provider = create(provider_name, provider_config.into(), model_cfg)?; + // Need at least two messages to summarize if messages.len() < 2 { return Err(ProviderError::ExecutionError( @@ -128,10 +141,6 @@ pub async fn generate_tooltip(messages: &[Message]) -> Result u64 { + 60 } -impl DatabricksAuth { - pub fn token(token: String) -> Self { - Self::Token(token) +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DatabricksProviderConfig { + pub host: String, + pub token: String, + #[serde(default)] + pub image_format: ImageFormat, + #[serde(default = "default_timeout")] + pub timeout: u64, // timeout in seconds +} + +impl DatabricksProviderConfig { + pub fn new(host: String, token: String) -> Self { + Self { + host, + token, + image_format: ImageFormat::OpenAi, + timeout: default_timeout(), + } + } + + pub fn from_env() -> Self { + let host = get_env("DATABRICKS_HOST").expect("Missing DATABRICKS_HOST"); + let token = get_env("DATABRICKS_TOKEN").expect("Missing DATABRICKS_TOKEN"); + Self::new(host, token) } } #[derive(Debug)] pub struct DatabricksProvider { - client: Client, - host: String, - auth: DatabricksAuth, + config: DatabricksProviderConfig, model: ModelConfig, - image_format: ImageFormat, + client: Client, +} + +impl DatabricksProvider { + pub fn from_env(model: ModelConfig) -> Self { + let config = DatabricksProviderConfig::from_env(); + DatabricksProvider::from_config(config, model) + .expect("Failed to initialize DatabricksProvider") + } } impl Default for DatabricksProvider { fn default() -> Self { + let config = DatabricksProviderConfig::from_env(); let model = ModelConfig::new(DATABRICKS_DEFAULT_MODEL.to_string()); - DatabricksProvider::from_env(model).expect("Failed to initialize Databricks provider") + DatabricksProvider::from_config(config, model) + .expect("Failed to initialize DatabricksProvider") } } impl DatabricksProvider { - pub fn from_env(model: ModelConfig) -> Result { - let host = get_env("DATABRICKS_HOST")?; - let api_key = get_env("DATABRICKS_TOKEN")?; - + pub fn from_config(config: DatabricksProviderConfig, model: ModelConfig) -> Result { let client = Client::builder() - .timeout(Duration::from_secs(600)) + .timeout(Duration::from_secs(config.timeout)) .build()?; Ok(Self { - client, - host, - auth: DatabricksAuth::token(api_key), + config, model, - image_format: ImageFormat::OpenAi, + client, }) } - async fn ensure_auth_header(&self) -> Result { - match &self.auth { - DatabricksAuth::Token(token) => Ok(format!("Bearer {}", token)), - } - } - async fn post(&self, payload: Value) -> Result { - let base_url = Url::parse(&self.host) + let base_url = Url::parse(&self.config.host) .map_err(|e| ProviderError::RequestFailed(format!("Invalid base URL: {e}")))?; let path = format!("serving-endpoints/{}/invocations", self.model.model_name); let url = base_url.join(&path).map_err(|e| { ProviderError::RequestFailed(format!("Failed to construct endpoint URL: {e}")) })?; - let auth_header = self.ensure_auth_header().await?; + let auth_header = format!("Bearer {}", &self.config.token); let response = self .client .post(url) @@ -189,7 +206,13 @@ impl Provider for DatabricksProvider { messages: &[Message], tools: &[Tool], ) -> Result { - let mut payload = create_request(&self.model, system, messages, tools, &self.image_format)?; + let mut payload = create_request( + &self.model, + system, + messages, + tools, + &self.config.image_format, + )?; // Remove the model key which is part of the url with databricks payload .as_object_mut() diff --git a/crates/goose-llm/src/providers/factory.rs b/crates/goose-llm/src/providers/factory.rs index 220f7ace3a..a70be3d44e 100644 --- a/crates/goose-llm/src/providers/factory.rs +++ b/crates/goose-llm/src/providers/factory.rs @@ -2,14 +2,28 @@ use std::sync::Arc; use anyhow::Result; -use super::{base::Provider, databricks::DatabricksProvider, openai::OpenAiProvider}; +use super::{ + base::Provider, + databricks::{DatabricksProvider, DatabricksProviderConfig}, + openai::{OpenAiProvider, OpenAiProviderConfig}, +}; use crate::model::ModelConfig; -pub fn create(name: &str, model: ModelConfig) -> Result> { +pub fn create( + name: &str, + provider_config: serde_json::Value, + model: ModelConfig, +) -> Result> { // We use Arc instead of Box to be able to clone for multiple async tasks match name { - "openai" => Ok(Arc::new(OpenAiProvider::from_env(model)?)), - "databricks" => Ok(Arc::new(DatabricksProvider::from_env(model)?)), + "openai" => { + let config: OpenAiProviderConfig = serde_json::from_value(provider_config)?; + Ok(Arc::new(OpenAiProvider::from_config(config, model)?)) + } + "databricks" => { + let config: DatabricksProviderConfig = serde_json::from_value(provider_config)?; + Ok(Arc::new(DatabricksProvider::from_config(config, model)?)) + } _ => Err(anyhow::anyhow!("Unknown provider: {}", name)), } } diff --git a/crates/goose-llm/src/providers/openai.rs b/crates/goose-llm/src/providers/openai.rs index b08c094dc7..bc0dc08848 100644 --- a/crates/goose-llm/src/providers/openai.rs +++ b/crates/goose-llm/src/providers/openai.rs @@ -3,6 +3,7 @@ use std::{collections::HashMap, time::Duration}; use anyhow::Result; use async_trait::async_trait; use reqwest::Client; +use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use super::{ @@ -20,82 +21,112 @@ use crate::{ pub const OPEN_AI_DEFAULT_MODEL: &str = "gpt-4o"; pub const _OPEN_AI_KNOWN_MODELS: &[&str] = &["gpt-4o", "gpt-4.1", "o1", "o3", "o4-mini"]; +fn default_timeout() -> u64 { + 60 +} + +fn default_base_path() -> String { + "v1/chat/completions".to_string() +} + +fn default_host() -> String { + "https://api.openai.com".to_string() +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OpenAiProviderConfig { + pub api_key: String, + #[serde(default = "default_host")] + pub host: String, + #[serde(default)] + pub organization: Option, + #[serde(default = "default_base_path")] + pub base_path: String, + #[serde(default)] + pub project: Option, + #[serde(default)] + pub custom_headers: Option>, + #[serde(default = "default_timeout")] + pub timeout: u64, // timeout in seconds +} + +impl OpenAiProviderConfig { + pub fn new(api_key: String) -> Self { + Self { + api_key, + host: default_host(), + organization: None, + base_path: default_base_path(), + project: None, + custom_headers: None, + timeout: 600, + } + } + + pub fn from_env() -> Self { + let api_key = get_env("OPENAI_API_KEY").expect("Missing OPENAI_API_KEY"); + Self::new(api_key) + } +} + #[derive(Debug)] pub struct OpenAiProvider { - client: Client, - host: String, - base_path: String, - api_key: String, - organization: Option, - project: Option, + config: OpenAiProviderConfig, model: ModelConfig, - custom_headers: Option>, + client: Client, +} + +impl OpenAiProvider { + pub fn from_env(model: ModelConfig) -> Self { + let config = OpenAiProviderConfig::from_env(); + OpenAiProvider::from_config(config, model).expect("Failed to initialize OpenAiProvider") + } } impl Default for OpenAiProvider { fn default() -> Self { + let config = OpenAiProviderConfig::from_env(); let model = ModelConfig::new(OPEN_AI_DEFAULT_MODEL.to_string()); - OpenAiProvider::from_env(model).expect("Failed to initialize OpenAI provider") + OpenAiProvider::from_config(config, model).expect("Failed to initialize OpenAiProvider") } } impl OpenAiProvider { - pub fn from_env(model: ModelConfig) -> Result { - let api_key: String = get_env("OPENAI_API_KEY")?; - let host: String = - get_env("OPENAI_HOST").unwrap_or_else(|_| "https://api.openai.com".to_string()); - let base_path: String = - get_env("OPENAI_BASE_PATH").unwrap_or_else(|_| "v1/chat/completions".to_string()); - let organization: Option = get_env("OPENAI_ORGANIZATION").ok(); - let project: Option = get_env("OPENAI_PROJECT").ok(); - let custom_headers: Option> = get_env("OPENAI_CUSTOM_HEADERS") - .or_else(|_| get_env("OPENAI_CUSTOM_HEADERS")) - .ok() - .map(parse_custom_headers); - // parse get_env("OPENAI_TIMEOUT") to u64 or set default to 600 - let timeout_secs = get_env("OPENAI_TIMEOUT") - .ok() - .and_then(|s| s.parse::().ok()) - .unwrap_or(600); + pub fn from_config(config: OpenAiProviderConfig, model: ModelConfig) -> Result { let client = Client::builder() - .timeout(Duration::from_secs(timeout_secs)) + .timeout(Duration::from_secs(config.timeout)) .build()?; Ok(Self { - client, - host, - base_path, - api_key, - organization, - project, + config, model, - custom_headers, + client, }) } async fn post(&self, payload: Value) -> Result { - let base_url = url::Url::parse(&self.host) + let base_url = url::Url::parse(&self.config.host) .map_err(|e| ProviderError::RequestFailed(format!("Invalid base URL: {e}")))?; - let url = base_url.join(&self.base_path).map_err(|e| { + let url = base_url.join(&self.config.base_path).map_err(|e| { ProviderError::RequestFailed(format!("Failed to construct endpoint URL: {e}")) })?; let mut request = self .client .post(url) - .header("Authorization", format!("Bearer {}", self.api_key)); + .header("Authorization", format!("Bearer {}", self.config.api_key)); // Add organization header if present - if let Some(org) = &self.organization { + if let Some(org) = &self.config.organization { request = request.header("OpenAI-Organization", org); } // Add project header if present - if let Some(project) = &self.project { + if let Some(project) = &self.config.project { request = request.header("OpenAI-Project", project); } - if let Some(custom_headers) = &self.custom_headers { + if let Some(custom_headers) = &self.config.custom_headers { for (key, value) in custom_headers { request = request.header(key, value); } @@ -198,14 +229,3 @@ impl Provider for OpenAiProvider { Ok(ProviderExtractResponse::new(data, model, usage)) } } - -fn parse_custom_headers(s: String) -> HashMap { - s.split(',') - .filter_map(|header| { - let mut parts = header.splitn(2, '='); - let key = parts.next().map(|s| s.trim().to_string())?; - let value = parts.next().map(|s| s.trim().to_string())?; - Some((key, value)) - }) - .collect() -} diff --git a/crates/goose-llm/src/providers/utils.rs b/crates/goose-llm/src/providers/utils.rs index 3d6f177434..914bad447e 100644 --- a/crates/goose-llm/src/providers/utils.rs +++ b/crates/goose-llm/src/providers/utils.rs @@ -19,12 +19,22 @@ struct OpenAIErrorResponse { error: OpenAIError, } -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Serialize, Deserialize, Default)] pub enum ImageFormat { + #[default] OpenAi, Anthropic, } +/// Timeout in seconds. +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct Timeout(u32); +impl Default for Timeout { + fn default() -> Self { + Timeout(60) + } +} + /// Convert an image content into an image json based on format pub fn convert_image(image: &ImageContent, image_format: &ImageFormat) -> Value { match image_format { diff --git a/crates/goose-llm/src/types/completion.rs b/crates/goose-llm/src/types/completion.rs index ce460081d9..ed1afa8724 100644 --- a/crates/goose-llm/src/types/completion.rs +++ b/crates/goose-llm/src/types/completion.rs @@ -11,17 +11,64 @@ use crate::types::json_value_ffi::JsonValueFfi; use crate::{message::Message, providers::Usage}; use crate::{model::ModelConfig, providers::errors::ProviderError}; -// Lifetimes are not supported in Uniffi, cause other languages don't have them -// https://github.com/mozilla/uniffi-rs/issues/1526#issuecomment-1528851837 -#[derive(uniffi::Record)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct CompletionRequest { pub provider_name: String, + pub provider_config: serde_json::Value, pub model_config: ModelConfig, pub system_preamble: String, pub messages: Vec, pub extensions: Vec, } +impl CompletionRequest { + pub fn new( + provider_name: String, + provider_config: serde_json::Value, + model_config: ModelConfig, + system_preamble: String, + messages: Vec, + extensions: Vec, + ) -> Self { + Self { + provider_name, + provider_config, + model_config, + system_preamble, + messages, + extensions, + } + } +} + +#[uniffi::export] +pub fn create_completion_request( + provider_name: &str, + provider_config: JsonValueFfi, + model_config: ModelConfig, + system_preamble: &str, + messages: Vec, + extensions: Vec, +) -> CompletionRequest { + CompletionRequest::new( + provider_name.to_string(), + provider_config.into(), + model_config, + system_preamble.to_string(), + messages, + extensions, + ) +} + +uniffi::custom_type!(CompletionRequest, String, { + lower: |tc: &CompletionRequest| { + serde_json::to_string(&tc).unwrap() + }, + try_lift: |s: String| { + Ok(serde_json::from_str(&s).unwrap()) + }, +}); + // https://mozilla.github.io/uniffi-rs/latest/proc_macro/errors.html #[derive(Debug, Error, uniffi::Error)] #[uniffi(flat_error)] @@ -149,7 +196,7 @@ uniffi::custom_type!(ToolConfig, String, { // — Register the newtypes with UniFFI, converting via JSON strings — -#[derive(Debug, Clone, Serialize, uniffi::Record)] +#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)] pub struct ExtensionConfig { name: String, instructions: Option, diff --git a/crates/goose-llm/tests/extract_session_name.rs b/crates/goose-llm/tests/extract_session_name.rs index d8cd6e9bde..8e19007b8c 100644 --- a/crates/goose-llm/tests/extract_session_name.rs +++ b/crates/goose-llm/tests/extract_session_name.rs @@ -4,15 +4,32 @@ use goose_llm::extractors::generate_session_name; use goose_llm::message::Message; use goose_llm::providers::errors::ProviderError; -#[tokio::test] -async fn test_generate_session_name_success() -> Result<(), ProviderError> { - // Load .env for Databricks credentials +fn should_run_test() -> Result<(), String> { dotenv().ok(); - let has_creds = - std::env::var("DATABRICKS_HOST").is_ok() && std::env::var("DATABRICKS_TOKEN").is_ok(); - if !has_creds { - println!("Skipping generate_session_name test – Databricks creds not set"); - return Ok(()); + if std::env::var("DATABRICKS_HOST").is_err() { + return Err("Missing DATABRICKS_HOST".to_string()); + } + if std::env::var("DATABRICKS_TOKEN").is_err() { + return Err("Missing DATABRICKS_TOKEN".to_string()); + } + Ok(()) +} + +async fn _generate_session_name(messages: &[Message]) -> Result { + let provider_name = "databricks"; + let provider_config = serde_json::json!({ + "host": std::env::var("DATABRICKS_HOST").expect("Missing DATABRICKS_HOST"), + "token": std::env::var("DATABRICKS_TOKEN").expect("Missing DATABRICKS_TOKEN"), + }); + + generate_session_name(provider_name, provider_config.into(), messages).await +} + +#[tokio::test] +async fn test_generate_session_name_success() { + if should_run_test().is_err() { + println!("Skipping..."); + return; } // Build a few messages with at least two user messages @@ -22,7 +39,10 @@ async fn test_generate_session_name_success() -> Result<(), ProviderError> { Message::user().with_text("What’s the weather in New York tomorrow?"), ]; - let name = generate_session_name(&messages).await?; + let name = _generate_session_name(&messages) + .await + .expect("Failed to generate session name"); + println!("Generated session name: {:?}", name); // Should be non-empty and at most 4 words @@ -34,20 +54,23 @@ async fn test_generate_session_name_success() -> Result<(), ProviderError> { "Name must be 4 words or less, got {}: {}", word_count, name - ); - - Ok(()) + ) } #[tokio::test] async fn test_generate_session_name_no_user() { + if should_run_test().is_err() { + println!("Skipping 'test_generate_session_name_no_user'. Databricks creds not set"); + return; + } + // No user messages → expect ExecutionError let messages = vec![ Message::assistant().with_text("System starting…"), Message::assistant().with_text("All systems go."), ]; - let err = generate_session_name(&messages).await; + let err = _generate_session_name(&messages).await; assert!( matches!(err, Err(ProviderError::ExecutionError(_))), "Expected ExecutionError when there are no user messages, got: {:?}", diff --git a/crates/goose-llm/tests/extract_tooltip.rs b/crates/goose-llm/tests/extract_tooltip.rs index 488b33d7c4..808ede57f1 100644 --- a/crates/goose-llm/tests/extract_tooltip.rs +++ b/crates/goose-llm/tests/extract_tooltip.rs @@ -6,13 +6,32 @@ use goose_llm::providers::errors::ProviderError; use goose_llm::types::core::{Content, ToolCall}; use serde_json::json; -#[tokio::test] -async fn test_generate_tooltip_simple() -> Result<(), ProviderError> { - // Skip if no Databricks creds +fn should_run_test() -> Result<(), String> { dotenv().ok(); - if std::env::var("DATABRICKS_HOST").is_err() || std::env::var("DATABRICKS_TOKEN").is_err() { - println!("Skipping simple tooltip test – Databricks creds not set"); - return Ok(()); + if std::env::var("DATABRICKS_HOST").is_err() { + return Err("Missing DATABRICKS_HOST".to_string()); + } + if std::env::var("DATABRICKS_TOKEN").is_err() { + return Err("Missing DATABRICKS_TOKEN".to_string()); + } + Ok(()) +} + +async fn _generate_tooltip(messages: &[Message]) -> Result { + let provider_name = "databricks"; + let provider_config = serde_json::json!({ + "host": std::env::var("DATABRICKS_HOST").expect("Missing DATABRICKS_HOST"), + "token": std::env::var("DATABRICKS_TOKEN").expect("Missing DATABRICKS_TOKEN"), + }); + + generate_tooltip(provider_name, provider_config.into(), messages).await +} + +#[tokio::test] +async fn test_generate_tooltip_simple() { + if should_run_test().is_err() { + println!("Skipping..."); + return; } // Two plain-text messages @@ -21,7 +40,9 @@ async fn test_generate_tooltip_simple() -> Result<(), ProviderError> { Message::assistant().with_text("I'm fine, thanks! How can I help?"), ]; - let tooltip = generate_tooltip(&messages).await?; + let tooltip = _generate_tooltip(&messages) + .await + .expect("Failed to generate tooltip"); println!("Generated tooltip: {:?}", tooltip); assert!(!tooltip.trim().is_empty(), "Tooltip must not be empty"); @@ -29,16 +50,13 @@ async fn test_generate_tooltip_simple() -> Result<(), ProviderError> { tooltip.len() < 100, "Tooltip should be reasonably short (<100 chars)" ); - Ok(()) } #[tokio::test] -async fn test_generate_tooltip_with_tools() -> Result<(), ProviderError> { - // Skip if no Databricks creds - dotenv().ok(); - if std::env::var("DATABRICKS_HOST").is_err() || std::env::var("DATABRICKS_TOKEN").is_err() { - println!("Skipping tool‐based tooltip test – Databricks creds not set"); - return Ok(()); +async fn test_generate_tooltip_with_tools() { + if should_run_test().is_err() { + println!("Skipping..."); + return; } // 1) Assistant message with a tool request @@ -57,7 +75,9 @@ async fn test_generate_tooltip_with_tools() -> Result<(), ProviderError> { let messages = vec![tool_req_msg, tool_resp_msg]; - let tooltip = generate_tooltip(&messages).await?; + let tooltip = _generate_tooltip(&messages) + .await + .expect("Failed to generate tooltip"); println!("Generated tooltip (tools): {:?}", tooltip); assert!(!tooltip.trim().is_empty(), "Tooltip must not be empty"); @@ -65,5 +85,4 @@ async fn test_generate_tooltip_with_tools() -> Result<(), ProviderError> { tooltip.len() < 100, "Tooltip should be reasonably short (<100 chars)" ); - Ok(()) } diff --git a/crates/goose-llm/tests/providers_extract.rs b/crates/goose-llm/tests/providers_extract.rs index 544040f7e1..83ab9f5024 100644 --- a/crates/goose-llm/tests/providers_extract.rs +++ b/crates/goose-llm/tests/providers_extract.rs @@ -25,8 +25,8 @@ impl ProviderType { fn create_provider(&self, cfg: ModelConfig) -> Result> { Ok(match self { - ProviderType::OpenAi => Arc::new(OpenAiProvider::from_env(cfg)?), - ProviderType::Databricks => Arc::new(DatabricksProvider::from_env(cfg)?), + ProviderType::OpenAi => Arc::new(OpenAiProvider::from_env(cfg)), + ProviderType::Databricks => Arc::new(DatabricksProvider::from_env(cfg)), }) } }