Expose tool calling support
Co-Authored-By: siGit Code <noreply@sigit.si>
Seto Elkahfi committed
Sep 9, 2026 at 23:11 UTC
c4ae4ad51d316e42138c4a24ed103ee320253ba9
15 files changed
+113
-11
Cargo.lock
+1
-1
@@ -3508,7 +3508,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
3508
3509
[[package]]
3510
name = "onde"
3511
-version = "1.2.4"
3511
+version = "1.2.5"
3512
dependencies = [
3513
"anyhow",
3514
"cc",
Cargo.toml
+1
-1
@@ -1,6 +1,6 @@
1
[package]
2
name = "onde"
3
-version = "1.2.4"
3
+version = "1.2.5"
4
edition = "2021"
5
description = "On-device inference engine for Apple silicon."
6
license = "MIT OR Apache-2.0"
sdk/dart/CHANGELOG.md
+4
@@ -1,3 +1,7 @@
1
+## 1.2.5
2
+
3
+- Added engine-level tool-calling capability detection and tool-result history support for agent SDKs
4
+
5
## 1.2.4
6
7
- Dependency updates across the Rust tree
sdk/dart/pubspec.yaml
+1
-1
@@ -2,7 +2,7 @@ name: onde_inference
2
description: >-
3
On-device LLM inference for Flutter & Dart. Run Qwen 2.5 models locally
4
with Metal on iOS and macOS, CPU on Android and desktop. No cloud, no API key.
5
-version: 1.2.4
5
+version: 1.2.5
6
repository: https://github.com/ondeinference/onde
7
homepage: https://ondeinference.com
8
documentation: https://ondeinference.com/sdk/dart
sdk/dart/rust/Cargo.lock
+1
-1
@@ -3604,7 +3604,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
3604
3605
[[package]]
3606
name = "onde"
3607
-version = "1.2.4"
3607
+version = "1.2.5"
3608
dependencies = [
3609
"anyhow",
3610
"cc",
sdk/kotlin/gradle.properties
+1
-1
@@ -10,7 +10,7 @@ kotlin.code.style=official
10
# Maven publishing coordinates — keep in sync with Cargo.toml [package] version
11
GROUP=com.ondeinference
12
POM_ARTIFACT_ID=onde-inference
13
-VERSION_NAME=1.2.4
13
+VERSION_NAME=1.2.5
14
15
16
sdk/react-native/CHANGELOG.md
+4
@@ -1,3 +1,7 @@
1
+## 1.2.5
2
+
3
+- Added engine-level tool-calling capability detection and tool-result history support for agent SDKs
4
+
5
## 1.2.4
6
7
- Dependency updates across the Rust tree
sdk/react-native/package-lock.json
+2
-2
@@ -1,12 +1,12 @@
1
{
2
"name": "@ondeinference/react-native",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
"lockfileVersion": 3,
5
"requires": true,
6
"packages": {
7
"": {
8
"name": "@ondeinference/react-native",
9
- "version": "1.2.4",
9
+ "version": "1.2.5",
10
"license": "MIT OR Apache-2.0",
11
"devDependencies": {
12
"expo-modules-core": "^2.0.0",
sdk/react-native/package.json
+1
-1
@@ -1,6 +1,6 @@
1
{
2
"name": "@ondeinference/react-native",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
"description": "On-device LLM inference for React Native. Run Qwen 2.5 models locally with Metal on iOS, CPU on Android. No cloud, no API key.",
5
"main": "build/index.js",
6
"types": "build/index.d.ts",
sdk/react-native/rust/Cargo.lock
+2
-2
@@ -3420,7 +3420,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
3420
3421
[[package]]
3422
name = "onde"
3423
-version = "1.2.4"
3423
+version = "1.2.5"
3424
dependencies = [
3425
"anyhow",
3426
"cc",
@@ -3777,7 +3777,7 @@ dependencies = [
3777
3778
[[package]]
3779
name = "onde-react-native"
3780
-version = "1.2.4"
3780
+version = "1.2.5"
3781
dependencies = [
3782
"jni",
3783
"log",
sdk/react-native/rust/Cargo.toml
+1
-1
@@ -1,6 +1,6 @@
1
[package]
2
name = "onde-react-native"
3
-version = "1.2.4"
3
+version = "1.2.5"
4
edition = "2021"
5
publish = false
6
src/inference/engine.rs
+39
@@ -278,6 +278,22 @@ pub struct ChatEngine {
278
target_os = "android"
279
))]
280
impl ChatEngine {
281
+ /// Whether the currently loaded model is verified for structured tool calls.
282
+ pub async fn tool_calling_support(&self) -> super::types::ToolCallingSupport {
283
+ use super::types::ToolCallingSupport;
284
+
285
+ let guard = self.inner.lock().await;
286
+ let Some(loaded) = guard.as_ref() else {
287
+ return ToolCallingSupport::Unknown;
288
+ };
289
+ let model_id = match &loaded.config {
290
+ LoadedModelConfig::Gguf(config) => &config.model_id,
291
+ LoadedModelConfig::Uqff(config) => &config.model_id,
292
+ #[cfg(target_os = "macos")]
293
+ LoadedModelConfig::Isq(config) => &config.model_id,
294
+ };
295
+ super::models::tool_calling_support(model_id)
296
+ }
297
// ── Construction ─────────────────────────────────────────────────────
298
299
/// Create a new engine with no model loaded and no Onde app association.
@@ -1482,6 +1498,23 @@ impl ChatEngine {
1498
})
1499
}
1500
1501
+ /// Append tool results to history without starting another inference round.
1502
+ ///
1503
+ /// Agent hosts use this when a turn is cancelled after the assistant has
1504
+ /// requested tools. Recording a result for every request keeps the history
1505
+ /// structurally valid for the next turn.
1506
+ pub async fn record_tool_results(&self, results: Vec<ToolResult>) {
1507
+ let mut guard = self.inner.lock().await;
1508
+ if let Some(loaded) = guard.as_mut() {
1509
+ for result in results {
1510
+ loaded.history.push(HistoryEntry::ToolResult {
1511
+ tool_call_id: result.tool_call_id,
1512
+ content: result.content,
1513
+ });
1514
+ }
1515
+ }
1516
+ }
1517
+
1518
/// Stream tool execution results back to the model.
1519
///
1520
/// Similar to [`send_tool_results`](Self::send_tool_results) but returns
@@ -2036,6 +2069,12 @@ impl ChatEngine {
2069
2070
pub async fn push_history(&self, _message: ChatMessage) {}
2071
2072
+ pub async fn tool_calling_support(&self) -> super::types::ToolCallingSupport {
2073
+ super::types::ToolCallingSupport::Unknown
2074
+ }
2075
+
2076
+ pub async fn record_tool_results(&self, _results: Vec<ToolResult>) {}
2077
+
2078
pub async fn send_message(
2079
&self,
2080
_user_message: impl Into<String>,
src/inference/mod.rs
+1
@@ -81,6 +81,7 @@ pub use types::{
81
ToolAwareResult,
82
ToolCallInfo,
83
ToolCallRequest,
84
+ ToolCallingSupport,
85
ToolDefinition,
86
ToolResult,
87
UqffModelConfig,
src/inference/models.rs
+42
@@ -231,6 +231,26 @@ pub const SUPPORTED_MODELS: &[&str] = &[
231
THEBLOKE_DEEPSEEK_CODER_6_7B_INSTRUCT_GGUF,
232
];
233
234
+/// Return Onde's verified tool-calling capability for a model repository.
235
+///
236
+/// Qwen 3 models use mistral.rs' Qwen tool parser. Qwen 2.5 Coder 7B is the
237
+/// one verified Qwen 2.5 model in the built-in catalogue that also supports
238
+/// structured tool calls. Custom repositories remain `Unknown` rather than
239
+/// being rejected solely because Onde has not catalogued them.
240
+pub fn tool_calling_support(model_id: &str) -> super::types::ToolCallingSupport {
241
+ use super::types::ToolCallingSupport;
242
+
243
+ if model_id.to_ascii_lowercase().contains("qwen3")
244
+ || model_id == BARTOWSKI_QWEN25_CODER_7B_INSTRUCT_GGUF
245
+ {
246
+ ToolCallingSupport::Supported
247
+ } else if SUPPORTED_MODELS.contains(&model_id) {
248
+ ToolCallingSupport::Unsupported
249
+ } else {
250
+ ToolCallingSupport::Unknown
251
+ }
252
+}
253
+
254
/// Rich metadata for a supported model, used by the frontend to display
255
/// unavailable models that can be downloaded.
256
pub struct SupportedModelInfo {
@@ -416,6 +436,28 @@ pub fn tok_model_id_for_repo(hf_repo_id: &str) -> Option<&'static str> {
436
mod tests {
437
use super::*;
438
439
+ #[test]
440
+ fn tool_calling_capability_is_authoritative_for_known_and_custom_models() {
441
+ use super::super::types::ToolCallingSupport;
442
+
443
+ assert_eq!(
444
+ tool_calling_support(BARTOWSKI_QWEN3_1_7B_GGUF),
445
+ ToolCallingSupport::Supported
446
+ );
447
+ assert_eq!(
448
+ tool_calling_support(BARTOWSKI_QWEN25_CODER_7B_INSTRUCT_GGUF),
449
+ ToolCallingSupport::Supported
450
+ );
451
+ assert_eq!(
452
+ tool_calling_support(BARTOWSKI_QWEN25_1_5B_INSTRUCT_GGUF),
453
+ ToolCallingSupport::Unsupported
454
+ );
455
+ assert_eq!(
456
+ tool_calling_support("acme/custom-agent-model"),
457
+ ToolCallingSupport::Unknown
458
+ );
459
+ }
460
+
461
/// Every supported model must have a corresponding display-metadata entry
462
/// so the model-list UI can render it.
463
#[test]
src/inference/types.rs
+12
@@ -360,6 +360,18 @@ pub struct ToolResult {
360
pub content: String,
361
}
362
363
+/// Whether the loaded model can reliably produce structured tool calls.
364
+///
365
+/// `Unknown` is used for custom model repositories that Onde does not ship in
366
+/// its built-in catalogue. Callers may choose to try those models, but should
367
+/// surface that the capability has not been verified.
368
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
369
+pub enum ToolCallingSupport {
370
+ Supported,
371
+ Unsupported,
372
+ Unknown,
373
+}
374
+
375
// ── Streaming chunk ──────────────────────────────────────────────────────────
376
377
/// A single streaming token chunk from the inference engine.