Add Qwen 3 SDK configs and desktop download support
Seto Elkahfi committed
Jul 13, 2026 at 19:38 UTC
ab6cd5f734ab7a7e08a80133c10a60954ed61bda
21 files changed
+1122
-140
.agents/skills/sdk-regressions/SKILL.md
+62
-8
@@ -80,22 +80,76 @@ cargo +nightly check -Z build-std --target aarch64-apple-tvos-sim
80
81
---
82
83
-## Tier 3 — Android, Windows, Linux (stable, may need cross-linker)
83
+## Tier 3 — Android, Windows, Linux (stable, needs cross toolchains)
84
85
-These check that CPU-only code paths compile. They may fail on linker resolution
86
-if cross-linkers aren't installed, but `cargo check` (no linking) should pass:
85
+These check that CPU-only code paths compile. For this crate, `cargo check`
86
+still runs C build scripts from dependencies such as `aws-lc-sys` and `ring`,
87
+so absent cross GCC toolchains are real blockers, not harmless link-only
88
+failures.
89
90
```bash
91
# Android arm64 — CPU, requires hf-hub sandbox workaround
92
cargo check --target aarch64-linux-android
93
92
-# Windows — CPU
94
+# Windows MSVC — CPU, exact CI target; run on Windows or a Windows CI runner
95
cargo check --target x86_64-pc-windows-msvc
96
97
# Linux — CPU
98
cargo check --target x86_64-unknown-linux-gnu
99
```
100
101
+On macOS hosts without cross GCC installed, expect Linux checks to fail looking
102
+for `x86_64-linux-gnu-gcc`, and Windows GNU checks to fail looking for
103
+`x86_64-w64-mingw32-gcc`. Use the Colima/Docker flow below instead of treating
104
+those as code regressions.
105
+
106
+### macOS host fallback: Colima/Docker
107
+
108
+Use this when the goal is to run the Linux and Windows cross-checks on a macOS
109
+host and the host does not have cross GCC toolchains. On Apple Silicon, run a
110
+native `linux/arm64` container. Avoid `linux/amd64` emulation for this crate;
111
+the dependency build can crash under QEMU before reaching `onde` code.
112
+
113
+Give Colima enough memory before running these checks:
114
+
115
+```bash
116
+colima status
117
+colima stop
118
+colima start --memory 8
119
+```
120
+
121
+Run Linux and the local Windows GNU proxy check in the container:
122
+
123
+```bash
124
+docker run --rm --platform linux/arm64 \
125
+ -v "$PWD:/workspace" \
126
+ -v onde-cargo-registry:/usr/local/cargo/registry \
127
+ -v onde-cargo-git:/usr/local/cargo/git \
128
+ -w /workspace \
129
+ rust:1.92-bookworm \
130
+ bash -lc '
131
+ export PATH=/usr/local/cargo/bin:$PATH
132
+ apt-get update >/dev/null
133
+ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
134
+ gcc-x86-64-linux-gnu \
135
+ libc6-dev-amd64-cross \
136
+ linux-libc-dev-amd64-cross \
137
+ gcc-mingw-w64-x86-64 \
138
+ binutils-mingw-w64-x86-64 >/dev/null
139
+ rustup target add x86_64-unknown-linux-gnu x86_64-pc-windows-gnu >/dev/null
140
+ CARGO_BUILD_JOBS=1 cargo check --target x86_64-unknown-linux-gnu
141
+ CARGO_BUILD_JOBS=1 cargo check --target x86_64-pc-windows-gnu
142
+ '
143
+```
144
+
145
+`x86_64-pc-windows-gnu` exercises `#[cfg(target_os = "windows")]` and the
146
+CPU-only Windows path locally, but it is not a substitute for the CI
147
+`x86_64-pc-windows-msvc` check. The MSVC ABI check needs a Windows/MSVC
148
+toolchain, normally GitHub Actions `windows-latest`.
149
+
150
+If the container reaches `Checking onde ...` and then reports Rust errors, the
151
+cross toolchain is working. Fix the crate error and rerun the check.
152
+
153
---
154
155
## Full Regression Suite (copy-paste)
@@ -136,6 +190,7 @@ rustup target add aarch64-apple-ios aarch64-apple-ios-sim
190
rustup target add aarch64-linux-android armv7-linux-androideabi
191
rustup target add x86_64-linux-android i686-linux-android
192
rustup target add x86_64-pc-windows-msvc
193
+rustup target add x86_64-pc-windows-gnu
194
rustup target add x86_64-unknown-linux-gnu
195
196
# Tier 2 — tvOS (nightly, no `rustup target add` — uses -Z build-std)
@@ -513,7 +568,6 @@ consumers either way — but leaving it active makes the dry-run confusing.
568
569
---
570
516
-*Last updated: July 2025 — added hf-hub/mistralrs-core iOS/tvOS deps,
517
-cache path consistency checks, App Group convention, onde-mistralrs publish
518
-strategy, GresIQ/HF_TOKEN secrets documentation.*
519
-
571
+*Last updated: July 2026 — added Colima/Docker Linux and Windows GNU
572
+cross-check guidance for macOS hosts without cross GCC, plus the MSVC CI
573
+caveat.*
Cargo.lock
+4
-4
@@ -862,9 +862,9 @@ dependencies = [
862
863
[[package]]
864
name = "crossbeam-epoch"
865
-version = "0.9.18"
865
+version = "0.9.20"
866
source = "registry+https://github.com/rust-lang/crates.io-index"
867
-checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
867
+checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
868
dependencies = [
869
"crossbeam-utils",
870
]
@@ -3820,9 +3820,9 @@ dependencies = [
3820
3821
[[package]]
3822
name = "quinn-proto"
3823
-version = "0.11.14"
3823
+version = "0.11.15"
3824
source = "registry+https://github.com/rust-lang/crates.io-index"
3825
-checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
3825
+checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e"
3826
dependencies = [
3827
"aws-lc-rs",
3828
"bytes",
sdk/dart/lib/src/engine.dart
+30
@@ -255,6 +255,36 @@ abstract final class OndeInference {
255
/// Optimised for code-generation tasks on macOS and Linux desktop.
256
static GgufModelConfig qwen25Coder3bConfig() => api.qwen25Coder3BConfig();
257
258
+ /// Qwen 3 0.6B (GGUF Q4_K_M, ~0.5 GB).
259
+ static GgufModelConfig qwen306bConfig() => api.qwen306BConfig();
260
+
261
+ /// Qwen 3 1.7B (GGUF Q4_K_M, ~1.3 GB).
262
+ static GgufModelConfig qwen317bConfig() => api.qwen317BConfig();
263
+
264
+ /// Qwen 3 4B (GGUF Q4_K_M, ~2.5 GB).
265
+ static GgufModelConfig qwen34bConfig() => api.qwen34BConfig();
266
+
267
+ /// Qwen 3 8B (GGUF Q4_K_M, ~5 GB).
268
+ static GgufModelConfig qwen38bConfig() => api.qwen38BConfig();
269
+
270
+ /// Qwen 3 14B (GGUF Q4_K_M, ~8.4 GB).
271
+ static GgufModelConfig qwen314bConfig() => api.qwen314BConfig();
272
+
273
+ /// Qwen 3 32B (GGUF Q4_K_M, ~19.8 GB).
274
+ static GgufModelConfig qwen332bConfig() => api.qwen332BConfig();
275
+
276
+ /// Qwen 3 4B Instruct 2507, the non-thinking checkpoint (~2.5 GB).
277
+ static GgufModelConfig qwen34bInstruct2507Config() =>
278
+ api.qwen34BInstruct2507Config();
279
+
280
+ /// Qwen 3 4B Thinking 2507 (~2.5 GB).
281
+ static GgufModelConfig qwen34bThinking2507Config() =>
282
+ api.qwen34BThinking2507Config();
283
+
284
+ /// Qwen 3 30B-A3B Instruct 2507 mixture-of-experts (~18.6 GB).
285
+ static GgufModelConfig qwen330bA3bInstruct2507Config() =>
286
+ api.qwen330BA3BInstruct2507Config();
287
+
288
// -------------------------------------------------------------------------
289
// Sampling config factories
290
// -------------------------------------------------------------------------
sdk/dart/lib/src/frb_generated.dart/api.dart
+36
-2
@@ -3,14 +3,14 @@
3
4
// ignore_for_file: type=lint, unused_import, unnecessary_import
5
6
-// ignore_for_file: invalid_use_of_internal_member
6
+// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
7
8
import 'frb_generated.dart';
9
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
10
import 'package:freezed_annotation/freezed_annotation.dart' hide protected;
11
part 'api.freezed.dart';
12
13
-// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `assert_receiver_is_total_eq`, `assert_receiver_is_total_eq`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `eq`, `eq`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`
13
+// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `assert_fields_are_eq`, `assert_fields_are_eq`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `eq`, `eq`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`
14
15
/// Return the platform-appropriate default `GgufModelConfig`.
16
///
@@ -35,6 +35,40 @@ GgufModelConfig qwen25Coder15BConfig() =>
35
GgufModelConfig qwen25Coder3BConfig() =>
36
RustLib.instance.api.crateApiQwen25Coder3BConfig();
37
38
+/// `GgufModelConfig` for Qwen 3 0.6B Q4_K_M (~0.5 GB).
39
+GgufModelConfig qwen306BConfig() =>
40
+ RustLib.instance.api.crateApiQwen306BConfig();
41
+
42
+/// `GgufModelConfig` for Qwen 3 1.7B Q4_K_M (~1.3 GB).
43
+GgufModelConfig qwen317BConfig() =>
44
+ RustLib.instance.api.crateApiQwen317BConfig();
45
+
46
+/// `GgufModelConfig` for Qwen 3 4B Q4_K_M (~2.7 GB).
47
+GgufModelConfig qwen34BConfig() => RustLib.instance.api.crateApiQwen34BConfig();
48
+
49
+/// `GgufModelConfig` for Qwen 3 8B Q4_K_M (~5 GB).
50
+GgufModelConfig qwen38BConfig() => RustLib.instance.api.crateApiQwen38BConfig();
51
+
52
+/// `GgufModelConfig` for Qwen 3 14B Q4_K_M (~8.4 GB).
53
+GgufModelConfig qwen314BConfig() =>
54
+ RustLib.instance.api.crateApiQwen314BConfig();
55
+
56
+/// `GgufModelConfig` for Qwen 3 32B Q4_K_M (~19.8 GB).
57
+GgufModelConfig qwen332BConfig() =>
58
+ RustLib.instance.api.crateApiQwen332BConfig();
59
+
60
+/// `GgufModelConfig` for Qwen 3 4B Instruct 2507 Q4_K_M (~2.5 GB).
61
+GgufModelConfig qwen34BInstruct2507Config() =>
62
+ RustLib.instance.api.crateApiQwen34BInstruct2507Config();
63
+
64
+/// `GgufModelConfig` for Qwen 3 4B Thinking 2507 Q4_K_M (~2.5 GB).
65
+GgufModelConfig qwen34BThinking2507Config() =>
66
+ RustLib.instance.api.crateApiQwen34BThinking2507Config();
67
+
68
+/// `GgufModelConfig` for Qwen 3 30B-A3B Instruct 2507 (MoE) Q4_K_M (~18.6 GB).
69
+GgufModelConfig qwen330BA3BInstruct2507Config() =>
70
+ RustLib.instance.api.crateApiQwen330BA3BInstruct2507Config();
71
+
72
/// Default sampling config: `temperature=0.7`, `top_p=0.95`, `max_tokens=512`.
73
SamplingConfig defaultSamplingConfig() =>
74
RustLib.instance.api.crateApiDefaultSamplingConfig();
sdk/dart/lib/src/frb_generated.dart/frb_generated.dart
+227
-2
@@ -3,7 +3,7 @@
3
4
// ignore_for_file: type=lint, unused_import, unnecessary_import
5
6
-// ignore_for_file: unused_element, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
6
+// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
7
8
import 'api.dart';
9
import 'dart:async';
@@ -66,7 +66,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
66
String get codegenVersion => '2.12.0';
67
68
@override
69
- int get rustContentHash => -467241906;
69
+ int get rustContentHash => -1719313298;
70
71
static const kDefaultExternalLibraryLoaderConfig =
72
ExternalLibraryLoaderConfig(
@@ -164,6 +164,24 @@ abstract class RustLibApi extends BaseApi {
164
165
GgufModelConfig crateApiQwen25Coder3BConfig();
166
167
+ GgufModelConfig crateApiQwen306BConfig();
168
+
169
+ GgufModelConfig crateApiQwen314BConfig();
170
+
171
+ GgufModelConfig crateApiQwen317BConfig();
172
+
173
+ GgufModelConfig crateApiQwen330BA3BInstruct2507Config();
174
+
175
+ GgufModelConfig crateApiQwen332BConfig();
176
+
177
+ GgufModelConfig crateApiQwen34BConfig();
178
+
179
+ GgufModelConfig crateApiQwen34BInstruct2507Config();
180
+
181
+ GgufModelConfig crateApiQwen34BThinking2507Config();
182
+
183
+ GgufModelConfig crateApiQwen38BConfig();
184
+
185
RustArcIncrementStrongCountFnType
186
get rust_arc_increment_strong_count_OndeChatEngine;
187
@@ -941,6 +959,213 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
959
TaskConstMeta get kCrateApiQwen25Coder3BConfigConstMeta =>
960
const TaskConstMeta(debugName: "qwen25_coder_3b_config", argNames: []);
961
962
+ @override
963
+ GgufModelConfig crateApiQwen306BConfig() {
964
+ return handler.executeSync(
965
+ SyncTask(
966
+ callFfi: () {
967
+ final serializer = SseSerializer(generalizedFrbRustBinding);
968
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 25)!;
969
+ },
970
+ codec: SseCodec(
971
+ decodeSuccessData: sse_decode_gguf_model_config,
972
+ decodeErrorData: null,
973
+ ),
974
+ constMeta: kCrateApiQwen306BConfigConstMeta,
975
+ argValues: [],
976
+ apiImpl: this,
977
+ ),
978
+ );
979
+ }
980
+
981
+ TaskConstMeta get kCrateApiQwen306BConfigConstMeta =>
982
+ const TaskConstMeta(debugName: "qwen3_0_6b_config", argNames: []);
983
+
984
+ @override
985
+ GgufModelConfig crateApiQwen314BConfig() {
986
+ return handler.executeSync(
987
+ SyncTask(
988
+ callFfi: () {
989
+ final serializer = SseSerializer(generalizedFrbRustBinding);
990
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 26)!;
991
+ },
992
+ codec: SseCodec(
993
+ decodeSuccessData: sse_decode_gguf_model_config,
994
+ decodeErrorData: null,
995
+ ),
996
+ constMeta: kCrateApiQwen314BConfigConstMeta,
997
+ argValues: [],
998
+ apiImpl: this,
999
+ ),
1000
+ );
1001
+ }
1002
+
1003
+ TaskConstMeta get kCrateApiQwen314BConfigConstMeta =>
1004
+ const TaskConstMeta(debugName: "qwen3_14b_config", argNames: []);
1005
+
1006
+ @override
1007
+ GgufModelConfig crateApiQwen317BConfig() {
1008
+ return handler.executeSync(
1009
+ SyncTask(
1010
+ callFfi: () {
1011
+ final serializer = SseSerializer(generalizedFrbRustBinding);
1012
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 27)!;
1013
+ },
1014
+ codec: SseCodec(
1015
+ decodeSuccessData: sse_decode_gguf_model_config,
1016
+ decodeErrorData: null,
1017
+ ),
1018
+ constMeta: kCrateApiQwen317BConfigConstMeta,
1019
+ argValues: [],
1020
+ apiImpl: this,
1021
+ ),
1022
+ );
1023
+ }
1024
+
1025
+ TaskConstMeta get kCrateApiQwen317BConfigConstMeta =>
1026
+ const TaskConstMeta(debugName: "qwen3_1_7b_config", argNames: []);
1027
+
1028
+ @override
1029
+ GgufModelConfig crateApiQwen330BA3BInstruct2507Config() {
1030
+ return handler.executeSync(
1031
+ SyncTask(
1032
+ callFfi: () {
1033
+ final serializer = SseSerializer(generalizedFrbRustBinding);
1034
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 28)!;
1035
+ },
1036
+ codec: SseCodec(
1037
+ decodeSuccessData: sse_decode_gguf_model_config,
1038
+ decodeErrorData: null,
1039
+ ),
1040
+ constMeta: kCrateApiQwen330Ba3BInstruct2507ConfigConstMeta,
1041
+ argValues: [],
1042
+ apiImpl: this,
1043
+ ),
1044
+ );
1045
+ }
1046
+
1047
+ TaskConstMeta get kCrateApiQwen330Ba3BInstruct2507ConfigConstMeta =>
1048
+ const TaskConstMeta(
1049
+ debugName: "qwen3_30b_a3b_instruct_2507_config",
1050
+ argNames: [],
1051
+ );
1052
+
1053
+ @override
1054
+ GgufModelConfig crateApiQwen332BConfig() {
1055
+ return handler.executeSync(
1056
+ SyncTask(
1057
+ callFfi: () {
1058
+ final serializer = SseSerializer(generalizedFrbRustBinding);
1059
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 29)!;
1060
+ },
1061
+ codec: SseCodec(
1062
+ decodeSuccessData: sse_decode_gguf_model_config,
1063
+ decodeErrorData: null,
1064
+ ),
1065
+ constMeta: kCrateApiQwen332BConfigConstMeta,
1066
+ argValues: [],
1067
+ apiImpl: this,
1068
+ ),
1069
+ );
1070
+ }
1071
+
1072
+ TaskConstMeta get kCrateApiQwen332BConfigConstMeta =>
1073
+ const TaskConstMeta(debugName: "qwen3_32b_config", argNames: []);
1074
+
1075
+ @override
1076
+ GgufModelConfig crateApiQwen34BConfig() {
1077
+ return handler.executeSync(
1078
+ SyncTask(
1079
+ callFfi: () {
1080
+ final serializer = SseSerializer(generalizedFrbRustBinding);
1081
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 30)!;
1082
+ },
1083
+ codec: SseCodec(
1084
+ decodeSuccessData: sse_decode_gguf_model_config,
1085
+ decodeErrorData: null,
1086
+ ),
1087
+ constMeta: kCrateApiQwen34BConfigConstMeta,
1088
+ argValues: [],
1089
+ apiImpl: this,
1090
+ ),
1091
+ );
1092
+ }
1093
+
1094
+ TaskConstMeta get kCrateApiQwen34BConfigConstMeta =>
1095
+ const TaskConstMeta(debugName: "qwen3_4b_config", argNames: []);
1096
+
1097
+ @override
1098
+ GgufModelConfig crateApiQwen34BInstruct2507Config() {
1099
+ return handler.executeSync(
1100
+ SyncTask(
1101
+ callFfi: () {
1102
+ final serializer = SseSerializer(generalizedFrbRustBinding);
1103
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 31)!;
1104
+ },
1105
+ codec: SseCodec(
1106
+ decodeSuccessData: sse_decode_gguf_model_config,
1107
+ decodeErrorData: null,
1108
+ ),
1109
+ constMeta: kCrateApiQwen34BInstruct2507ConfigConstMeta,
1110
+ argValues: [],
1111
+ apiImpl: this,
1112
+ ),
1113
+ );
1114
+ }
1115
+
1116
+ TaskConstMeta get kCrateApiQwen34BInstruct2507ConfigConstMeta =>
1117
+ const TaskConstMeta(
1118
+ debugName: "qwen3_4b_instruct_2507_config",
1119
+ argNames: [],
1120
+ );
1121
+
1122
+ @override
1123
+ GgufModelConfig crateApiQwen34BThinking2507Config() {
1124
+ return handler.executeSync(
1125
+ SyncTask(
1126
+ callFfi: () {
1127
+ final serializer = SseSerializer(generalizedFrbRustBinding);
1128
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 32)!;
1129
+ },
1130
+ codec: SseCodec(
1131
+ decodeSuccessData: sse_decode_gguf_model_config,
1132
+ decodeErrorData: null,
1133
+ ),
1134
+ constMeta: kCrateApiQwen34BThinking2507ConfigConstMeta,
1135
+ argValues: [],
1136
+ apiImpl: this,
1137
+ ),
1138
+ );
1139
+ }
1140
+
1141
+ TaskConstMeta get kCrateApiQwen34BThinking2507ConfigConstMeta =>
1142
+ const TaskConstMeta(
1143
+ debugName: "qwen3_4b_thinking_2507_config",
1144
+ argNames: [],
1145
+ );
1146
+
1147
+ @override
1148
+ GgufModelConfig crateApiQwen38BConfig() {
1149
+ return handler.executeSync(
1150
+ SyncTask(
1151
+ callFfi: () {
1152
+ final serializer = SseSerializer(generalizedFrbRustBinding);
1153
+ return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 33)!;
1154
+ },
1155
+ codec: SseCodec(
1156
+ decodeSuccessData: sse_decode_gguf_model_config,
1157
+ decodeErrorData: null,
1158
+ ),
1159
+ constMeta: kCrateApiQwen38BConfigConstMeta,
1160
+ argValues: [],
1161
+ apiImpl: this,
1162
+ ),
1163
+ );
1164
+ }
1165
+
1166
+ TaskConstMeta get kCrateApiQwen38BConfigConstMeta =>
1167
+ const TaskConstMeta(debugName: "qwen3_8b_config", argNames: []);
1168
+
1169
RustArcIncrementStrongCountFnType
1170
get rust_arc_increment_strong_count_OndeChatEngine => wire
1171
.rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerOndeChatEngine;
sdk/dart/lib/src/frb_generated.dart/frb_generated.io.dart
+1
-1
@@ -3,7 +3,7 @@
3
4
// ignore_for_file: type=lint, unused_import, unnecessary_import
5
6
-// ignore_for_file: unused_element, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
6
+// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
7
8
import 'api.dart';
9
import 'dart:async';
sdk/dart/pubspec.yaml
+1
-1
@@ -39,7 +39,7 @@ dev_dependencies:
39
# freezed + build_runner are required by FRB 2.x codegen for union/sealed types.
40
# After running `flutter_rust_bridge_codegen generate`, also run:
41
# dart run build_runner build --delete-conflicting-outputs
42
- freezed: ^3.2.5
42
+ freezed: ^3.2.3
43
build_runner: ^2.4.9
44
# NOTE: The FFI code-generator is a Rust binary — NOT a Dart package.
45
# Install it once with:
sdk/dart/rust/src/api.rs
+3
@@ -1,3 +1,6 @@
1
+// Copyright 2026 Onde Inference (Splitfire AB). All rights reserved.
2
+// Use of this source code is governed by the MIT license.
3
+//
4
//! Flutter Rust Bridge v2 API for the Onde Inference Dart SDK.
5
//!
6
//! This module declares mirror types (re-declared plain Rust structs/enums
sdk/dart/rust/src/frb_generated.rs
+274
-1
@@ -39,7 +39,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
39
default_rust_auto_opaque = RustAutoOpaqueMoi,
40
);
41
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
42
-pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -467241906;
42
+pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1719313298;
43
44
// Section: executor
45
@@ -1178,6 +1178,268 @@ fn wire__crate__api__qwen25_coder_3b_config_impl(
1178
},
1179
)
1180
}
1181
+fn wire__crate__api__qwen3_0_6b_config_impl(
1182
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
1183
+ rust_vec_len_: i32,
1184
+ data_len_: i32,
1185
+) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
1186
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::<flutter_rust_bridge::for_generated::SseCodec, _>(
1187
+ flutter_rust_bridge::for_generated::TaskInfo {
1188
+ debug_name: "qwen3_0_6b_config",
1189
+ port: None,
1190
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync,
1191
+ },
1192
+ move || {
1193
+ let message = unsafe {
1194
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
1195
+ ptr_,
1196
+ rust_vec_len_,
1197
+ data_len_,
1198
+ )
1199
+ };
1200
+ let mut deserializer =
1201
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
1202
+ deserializer.end();
1203
+ transform_result_sse::<_, ()>((move || {
1204
+ let output_ok = Result::<_, ()>::Ok(crate::api::qwen3_0_6b_config())?;
1205
+ Ok(output_ok)
1206
+ })())
1207
+ },
1208
+ )
1209
+}
1210
+fn wire__crate__api__qwen3_14b_config_impl(
1211
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
1212
+ rust_vec_len_: i32,
1213
+ data_len_: i32,
1214
+) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
1215
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::<flutter_rust_bridge::for_generated::SseCodec, _>(
1216
+ flutter_rust_bridge::for_generated::TaskInfo {
1217
+ debug_name: "qwen3_14b_config",
1218
+ port: None,
1219
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync,
1220
+ },
1221
+ move || {
1222
+ let message = unsafe {
1223
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
1224
+ ptr_,
1225
+ rust_vec_len_,
1226
+ data_len_,
1227
+ )
1228
+ };
1229
+ let mut deserializer =
1230
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
1231
+ deserializer.end();
1232
+ transform_result_sse::<_, ()>((move || {
1233
+ let output_ok = Result::<_, ()>::Ok(crate::api::qwen3_14b_config())?;
1234
+ Ok(output_ok)
1235
+ })())
1236
+ },
1237
+ )
1238
+}
1239
+fn wire__crate__api__qwen3_1_7b_config_impl(
1240
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
1241
+ rust_vec_len_: i32,
1242
+ data_len_: i32,
1243
+) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
1244
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::<flutter_rust_bridge::for_generated::SseCodec, _>(
1245
+ flutter_rust_bridge::for_generated::TaskInfo {
1246
+ debug_name: "qwen3_1_7b_config",
1247
+ port: None,
1248
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync,
1249
+ },
1250
+ move || {
1251
+ let message = unsafe {
1252
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
1253
+ ptr_,
1254
+ rust_vec_len_,
1255
+ data_len_,
1256
+ )
1257
+ };
1258
+ let mut deserializer =
1259
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
1260
+ deserializer.end();
1261
+ transform_result_sse::<_, ()>((move || {
1262
+ let output_ok = Result::<_, ()>::Ok(crate::api::qwen3_1_7b_config())?;
1263
+ Ok(output_ok)
1264
+ })())
1265
+ },
1266
+ )
1267
+}
1268
+fn wire__crate__api__qwen3_30b_a3b_instruct_2507_config_impl(
1269
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
1270
+ rust_vec_len_: i32,
1271
+ data_len_: i32,
1272
+) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
1273
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::<flutter_rust_bridge::for_generated::SseCodec, _>(
1274
+ flutter_rust_bridge::for_generated::TaskInfo {
1275
+ debug_name: "qwen3_30b_a3b_instruct_2507_config",
1276
+ port: None,
1277
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync,
1278
+ },
1279
+ move || {
1280
+ let message = unsafe {
1281
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
1282
+ ptr_,
1283
+ rust_vec_len_,
1284
+ data_len_,
1285
+ )
1286
+ };
1287
+ let mut deserializer =
1288
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
1289
+ deserializer.end();
1290
+ transform_result_sse::<_, ()>((move || {
1291
+ let output_ok =
1292
+ Result::<_, ()>::Ok(crate::api::qwen3_30b_a3b_instruct_2507_config())?;
1293
+ Ok(output_ok)
1294
+ })())
1295
+ },
1296
+ )
1297
+}
1298
+fn wire__crate__api__qwen3_32b_config_impl(
1299
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
1300
+ rust_vec_len_: i32,
1301
+ data_len_: i32,
1302
+) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
1303
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::<flutter_rust_bridge::for_generated::SseCodec, _>(
1304
+ flutter_rust_bridge::for_generated::TaskInfo {
1305
+ debug_name: "qwen3_32b_config",
1306
+ port: None,
1307
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync,
1308
+ },
1309
+ move || {
1310
+ let message = unsafe {
1311
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
1312
+ ptr_,
1313
+ rust_vec_len_,
1314
+ data_len_,
1315
+ )
1316
+ };
1317
+ let mut deserializer =
1318
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
1319
+ deserializer.end();
1320
+ transform_result_sse::<_, ()>((move || {
1321
+ let output_ok = Result::<_, ()>::Ok(crate::api::qwen3_32b_config())?;
1322
+ Ok(output_ok)
1323
+ })())
1324
+ },
1325
+ )
1326
+}
1327
+fn wire__crate__api__qwen3_4b_config_impl(
1328
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
1329
+ rust_vec_len_: i32,
1330
+ data_len_: i32,
1331
+) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
1332
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::<flutter_rust_bridge::for_generated::SseCodec, _>(
1333
+ flutter_rust_bridge::for_generated::TaskInfo {
1334
+ debug_name: "qwen3_4b_config",
1335
+ port: None,
1336
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync,
1337
+ },
1338
+ move || {
1339
+ let message = unsafe {
1340
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
1341
+ ptr_,
1342
+ rust_vec_len_,
1343
+ data_len_,
1344
+ )
1345
+ };
1346
+ let mut deserializer =
1347
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
1348
+ deserializer.end();
1349
+ transform_result_sse::<_, ()>((move || {
1350
+ let output_ok = Result::<_, ()>::Ok(crate::api::qwen3_4b_config())?;
1351
+ Ok(output_ok)
1352
+ })())
1353
+ },
1354
+ )
1355
+}
1356
+fn wire__crate__api__qwen3_4b_instruct_2507_config_impl(
1357
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
1358
+ rust_vec_len_: i32,
1359
+ data_len_: i32,
1360
+) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
1361
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::<flutter_rust_bridge::for_generated::SseCodec, _>(
1362
+ flutter_rust_bridge::for_generated::TaskInfo {
1363
+ debug_name: "qwen3_4b_instruct_2507_config",
1364
+ port: None,
1365
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync,
1366
+ },
1367
+ move || {
1368
+ let message = unsafe {
1369
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
1370
+ ptr_,
1371
+ rust_vec_len_,
1372
+ data_len_,
1373
+ )
1374
+ };
1375
+ let mut deserializer =
1376
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
1377
+ deserializer.end();
1378
+ transform_result_sse::<_, ()>((move || {
1379
+ let output_ok = Result::<_, ()>::Ok(crate::api::qwen3_4b_instruct_2507_config())?;
1380
+ Ok(output_ok)
1381
+ })())
1382
+ },
1383
+ )
1384
+}
1385
+fn wire__crate__api__qwen3_4b_thinking_2507_config_impl(
1386
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
1387
+ rust_vec_len_: i32,
1388
+ data_len_: i32,
1389
+) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
1390
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::<flutter_rust_bridge::for_generated::SseCodec, _>(
1391
+ flutter_rust_bridge::for_generated::TaskInfo {
1392
+ debug_name: "qwen3_4b_thinking_2507_config",
1393
+ port: None,
1394
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync,
1395
+ },
1396
+ move || {
1397
+ let message = unsafe {
1398
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
1399
+ ptr_,
1400
+ rust_vec_len_,
1401
+ data_len_,
1402
+ )
1403
+ };
1404
+ let mut deserializer =
1405
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
1406
+ deserializer.end();
1407
+ transform_result_sse::<_, ()>((move || {
1408
+ let output_ok = Result::<_, ()>::Ok(crate::api::qwen3_4b_thinking_2507_config())?;
1409
+ Ok(output_ok)
1410
+ })())
1411
+ },
1412
+ )
1413
+}
1414
+fn wire__crate__api__qwen3_8b_config_impl(
1415
+ ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
1416
+ rust_vec_len_: i32,
1417
+ data_len_: i32,
1418
+) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
1419
+ FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::<flutter_rust_bridge::for_generated::SseCodec, _>(
1420
+ flutter_rust_bridge::for_generated::TaskInfo {
1421
+ debug_name: "qwen3_8b_config",
1422
+ port: None,
1423
+ mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync,
1424
+ },
1425
+ move || {
1426
+ let message = unsafe {
1427
+ flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
1428
+ ptr_,
1429
+ rust_vec_len_,
1430
+ data_len_,
1431
+ )
1432
+ };
1433
+ let mut deserializer =
1434
+ flutter_rust_bridge::for_generated::SseDeserializer::new(message);
1435
+ deserializer.end();
1436
+ transform_result_sse::<_, ()>((move || {
1437
+ let output_ok = Result::<_, ()>::Ok(crate::api::qwen3_8b_config())?;
1438
+ Ok(output_ok)
1439
+ })())
1440
+ },
1441
+ )
1442
+}
1443
1444
// Section: related_funcs
1445
@@ -1632,6 +1894,17 @@ fn pde_ffi_dispatcher_sync_impl(
1894
22 => wire__crate__api__qwen25_3b_config_impl(ptr, rust_vec_len, data_len),
1895
23 => wire__crate__api__qwen25_coder_1_5b_config_impl(ptr, rust_vec_len, data_len),
1896
24 => wire__crate__api__qwen25_coder_3b_config_impl(ptr, rust_vec_len, data_len),
1897
+ 25 => wire__crate__api__qwen3_0_6b_config_impl(ptr, rust_vec_len, data_len),
1898
+ 26 => wire__crate__api__qwen3_14b_config_impl(ptr, rust_vec_len, data_len),
1899
+ 27 => wire__crate__api__qwen3_1_7b_config_impl(ptr, rust_vec_len, data_len),
1900
+ 28 => {
1901
+ wire__crate__api__qwen3_30b_a3b_instruct_2507_config_impl(ptr, rust_vec_len, data_len)
1902
+ }
1903
+ 29 => wire__crate__api__qwen3_32b_config_impl(ptr, rust_vec_len, data_len),
1904
+ 30 => wire__crate__api__qwen3_4b_config_impl(ptr, rust_vec_len, data_len),
1905
+ 31 => wire__crate__api__qwen3_4b_instruct_2507_config_impl(ptr, rust_vec_len, data_len),
1906
+ 32 => wire__crate__api__qwen3_4b_thinking_2507_config_impl(ptr, rust_vec_len, data_len),
1907
+ 33 => wire__crate__api__qwen3_8b_config_impl(ptr, rust_vec_len, data_len),
1908
_ => unreachable!(),
1909
}
1910
}
sdk/dart/test/dart_test.dart
+77
@@ -14,6 +14,17 @@ class _FakeOndeChatEngine implements OndeChatEngine {
14
}
15
16
class _MockRustLibApi implements frb.RustLibApi {
17
+ static GgufModelConfig _qwen3(
18
+ String variant,
19
+ String filename,
20
+ String memory,
21
+ ) => GgufModelConfig(
22
+ modelId: 'bartowski/Qwen_Qwen3-$variant-GGUF',
23
+ files: [filename],
24
+ displayName: 'Qwen 3 $variant',
25
+ approxMemory: memory,
26
+ );
27
+
28
static const _qwen2515b = GgufModelConfig(
29
modelId: 'bartowski/Qwen2.5-1.5B-Instruct-GGUF',
30
files: ['Qwen2.5-1.5B-Instruct-Q4_K_M.gguf'],
@@ -87,6 +98,51 @@ class _MockRustLibApi implements frb.RustLibApi {
98
@override
99
GgufModelConfig crateApiQwen25Coder3BConfig() => _qwen25Coder3b;
100
101
+ @override
102
+ GgufModelConfig crateApiQwen306BConfig() =>
103
+ _qwen3('0.6B', 'Qwen_Qwen3-0.6B-Q4_K_M.gguf', '~0.5 GB');
104
+
105
+ @override
106
+ GgufModelConfig crateApiQwen317BConfig() =>
107
+ _qwen3('1.7B', 'Qwen_Qwen3-1.7B-Q4_K_M.gguf', '~1.3 GB');
108
+
109
+ @override
110
+ GgufModelConfig crateApiQwen34BConfig() =>
111
+ _qwen3('4B', 'Qwen_Qwen3-4B-Q4_K_M.gguf', '~2.5 GB');
112
+
113
+ @override
114
+ GgufModelConfig crateApiQwen38BConfig() =>
115
+ _qwen3('8B', 'Qwen_Qwen3-8B-Q4_K_M.gguf', '~5 GB');
116
+
117
+ @override
118
+ GgufModelConfig crateApiQwen314BConfig() =>
119
+ _qwen3('14B', 'Qwen_Qwen3-14B-Q4_K_M.gguf', '~8.4 GB');
120
+
121
+ @override
122
+ GgufModelConfig crateApiQwen332BConfig() =>
123
+ _qwen3('32B', 'Qwen_Qwen3-32B-Q4_K_M.gguf', '~19.8 GB');
124
+
125
+ @override
126
+ GgufModelConfig crateApiQwen34BInstruct2507Config() => _qwen3(
127
+ '4B-Instruct-2507',
128
+ 'Qwen_Qwen3-4B-Instruct-2507-Q4_K_M.gguf',
129
+ '~2.5 GB',
130
+ );
131
+
132
+ @override
133
+ GgufModelConfig crateApiQwen34BThinking2507Config() => _qwen3(
134
+ '4B-Thinking-2507',
135
+ 'Qwen_Qwen3-4B-Thinking-2507-Q4_K_M.gguf',
136
+ '~2.5 GB',
137
+ );
138
+
139
+ @override
140
+ GgufModelConfig crateApiQwen330BA3BInstruct2507Config() => _qwen3(
141
+ '30B-A3B-Instruct-2507',
142
+ 'Qwen_Qwen3-30B-A3B-Instruct-2507-Q4_K_M.gguf',
143
+ '~18.6 GB',
144
+ );
145
+
146
@override
147
dynamic noSuchMethod(Invocation invocation) =>
148
throw UnimplementedError('Unexpected RustLibApi member: $invocation');
@@ -201,6 +257,27 @@ void main() {
257
258
final defaultConfig = OndeInference.defaultModelConfig();
259
expect(defaultConfig.modelId, isNotEmpty);
260
+
261
+ final qwen3Configs = [
262
+ OndeInference.qwen306bConfig(),
263
+ OndeInference.qwen317bConfig(),
264
+ OndeInference.qwen34bConfig(),
265
+ OndeInference.qwen38bConfig(),
266
+ OndeInference.qwen314bConfig(),
267
+ OndeInference.qwen332bConfig(),
268
+ OndeInference.qwen34bInstruct2507Config(),
269
+ OndeInference.qwen34bThinking2507Config(),
270
+ OndeInference.qwen330bA3bInstruct2507Config(),
271
+ ];
272
+ expect(qwen3Configs, everyElement(isA<GgufModelConfig>()));
273
+ expect(
274
+ qwen3Configs,
275
+ everyElement(
276
+ predicate<GgufModelConfig>(
277
+ (config) => config.modelId.contains('Qwen3'),
278
+ ),
279
+ ),
280
+ );
281
});
282
283
test('equality', () {
sdk/kotlin/lib/src/shared/kotlin/com/ondeinference/onde/Convenience.kt
+4
-3
@@ -1,5 +1,6 @@
1
-// Copyright 2025 Onde Inference (Splitfire AB). All rights reserved.
2
-// SPDX-License-Identifier: MIT OR Apache-2.0
1
+// Copyright 2026 Onde Inference (Splitfire AB). All rights reserved.
2
+// Use of this source code is governed by the MIT license.
3
+//
4
5
package com.ondeinference.onde
6
@@ -41,7 +42,7 @@ object OndeModels {
42
fun qwen3_32b(): GgufModelConfig = uniffi.onde.qwen332bConfig()
43
/** Qwen 3 4B Instruct 2507 GGUF Q4_K_M (~2.5 GB) — latest non-thinking 4B. */
44
fun qwen3_4b_instruct_2507(): GgufModelConfig = uniffi.onde.qwen34bInstruct2507Config()
44
- /** Qwen 3 4B Thinking 2507 GGUF Q4_K_M (~2.5 GB) — latest reasoning 4B. */
45
+ /** Qwen 3 4B Thinking 2507 (~2.5 GB); nil sampling automatically uses 4096 tokens. */
46
fun qwen3_4b_thinking_2507(): GgufModelConfig = uniffi.onde.qwen34bThinking2507Config()
47
/** Qwen 3 30B-A3B Instruct 2507 GGUF Q4_K_M (~18.6 GB) — flagship MoE. */
48
fun qwen3_30b_a3b_instruct_2507(): GgufModelConfig = uniffi.onde.qwen330bA3bInstruct2507Config()
sdk/react-native/android/src/main/java/com/ondeinference/OndeInferenceModule.kt
+21
@@ -1,3 +1,6 @@
1
+// Copyright 2026 Onde Inference (Splitfire AB). All rights reserved.
2
+// Use of this source code is governed by the MIT license.
3
+//
4
package com.ondeinference
5
6
import expo.modules.kotlin.modules.Module
@@ -48,6 +51,15 @@ class OndeInferenceModule : Module() {
51
private external fun nativeDefaultModelConfig(): String
52
private external fun nativeQwen251_5bConfig(): String
53
private external fun nativeQwen253bConfig(): String
54
+ private external fun nativeQwen3_0_6bConfig(): String
55
+ private external fun nativeQwen3_1_7bConfig(): String
56
+ private external fun nativeQwen3_4bConfig(): String
57
+ private external fun nativeQwen3_8bConfig(): String
58
+ private external fun nativeQwen3_14bConfig(): String
59
+ private external fun nativeQwen3_32bConfig(): String
60
+ private external fun nativeQwen3_4bInstruct2507Config(): String
61
+ private external fun nativeQwen3_4bThinking2507Config(): String
62
+ private external fun nativeQwen3_30bA3bInstruct2507Config(): String
63
private external fun nativeDefaultSamplingConfig(): String
64
private external fun nativeDeterministicSamplingConfig(): String
65
private external fun nativeMobileSamplingConfig(): String
@@ -101,6 +113,15 @@ class OndeInferenceModule : Module() {
113
Function("defaultModelConfig") { nativeDefaultModelConfig() }
114
Function("qwen251_5bConfig") { nativeQwen251_5bConfig() }
115
Function("qwen253bConfig") { nativeQwen253bConfig() }
116
+ Function("qwen3_0_6bConfig") { nativeQwen3_0_6bConfig() }
117
+ Function("qwen3_1_7bConfig") { nativeQwen3_1_7bConfig() }
118
+ Function("qwen3_4bConfig") { nativeQwen3_4bConfig() }
119
+ Function("qwen3_8bConfig") { nativeQwen3_8bConfig() }
120
+ Function("qwen3_14bConfig") { nativeQwen3_14bConfig() }
121
+ Function("qwen3_32bConfig") { nativeQwen3_32bConfig() }
122
+ Function("qwen3_4bInstruct2507Config") { nativeQwen3_4bInstruct2507Config() }
123
+ Function("qwen3_4bThinking2507Config") { nativeQwen3_4bThinking2507Config() }
124
+ Function("qwen3_30bA3bInstruct2507Config") { nativeQwen3_30bA3bInstruct2507Config() }
125
Function("defaultSamplingConfig") { nativeDefaultSamplingConfig() }
126
Function("deterministicSamplingConfig") { nativeDeterministicSamplingConfig() }
127
Function("mobileSamplingConfig") { nativeMobileSamplingConfig() }
sdk/react-native/ios/OndeInferenceModule.swift
+69
@@ -1,3 +1,6 @@
1
+// Copyright 2026 Onde Inference (Splitfire AB). All rights reserved.
2
+// Use of this source code is governed by the MIT license.
3
+//
4
import ExpoModulesCore
5
import Foundation
6
@@ -86,6 +89,33 @@ func onde_qwen25_1_5b_config() -> UnsafeMutablePointer<CChar>?
89
@_silgen_name("onde_qwen25_3b_config")
90
func onde_qwen25_3b_config() -> UnsafeMutablePointer<CChar>?
91
92
+@_silgen_name("onde_qwen3_0_6b_config")
93
+func onde_qwen3_0_6b_config() -> UnsafeMutablePointer<CChar>?
94
+
95
+@_silgen_name("onde_qwen3_1_7b_config")
96
+func onde_qwen3_1_7b_config() -> UnsafeMutablePointer<CChar>?
97
+
98
+@_silgen_name("onde_qwen3_4b_config")
99
+func onde_qwen3_4b_config() -> UnsafeMutablePointer<CChar>?
100
+
101
+@_silgen_name("onde_qwen3_8b_config")
102
+func onde_qwen3_8b_config() -> UnsafeMutablePointer<CChar>?
103
+
104
+@_silgen_name("onde_qwen3_14b_config")
105
+func onde_qwen3_14b_config() -> UnsafeMutablePointer<CChar>?
106
+
107
+@_silgen_name("onde_qwen3_32b_config")
108
+func onde_qwen3_32b_config() -> UnsafeMutablePointer<CChar>?
109
+
110
+@_silgen_name("onde_qwen3_4b_instruct_2507_config")
111
+func onde_qwen3_4b_instruct_2507_config() -> UnsafeMutablePointer<CChar>?
112
+
113
+@_silgen_name("onde_qwen3_4b_thinking_2507_config")
114
+func onde_qwen3_4b_thinking_2507_config() -> UnsafeMutablePointer<CChar>?
115
+
116
+@_silgen_name("onde_qwen3_30b_a3b_instruct_2507_config")
117
+func onde_qwen3_30b_a3b_instruct_2507_config() -> UnsafeMutablePointer<CChar>?
118
+
119
// Sampling config free functions
120
@_silgen_name("onde_default_sampling_config")
121
func onde_default_sampling_config() -> UnsafeMutablePointer<CChar>?
@@ -270,6 +300,45 @@ public class OndeInferenceModule: Module {
300
return try self.validateJsonResult(result)
301
}
302
303
+ Function("qwen3_0_6bConfig") { () -> String in
304
+ try self.validateJsonResult(self.consumeRustString(onde_qwen3_0_6b_config()))
305
+ }
306
+
307
+ Function("qwen3_1_7bConfig") { () -> String in
308
+ try self.validateJsonResult(self.consumeRustString(onde_qwen3_1_7b_config()))
309
+ }
310
+
311
+ Function("qwen3_4bConfig") { () -> String in
312
+ try self.validateJsonResult(self.consumeRustString(onde_qwen3_4b_config()))
313
+ }
314
+
315
+ Function("qwen3_8bConfig") { () -> String in
316
+ try self.validateJsonResult(self.consumeRustString(onde_qwen3_8b_config()))
317
+ }
318
+
319
+ Function("qwen3_14bConfig") { () -> String in
320
+ try self.validateJsonResult(self.consumeRustString(onde_qwen3_14b_config()))
321
+ }
322
+
323
+ Function("qwen3_32bConfig") { () -> String in
324
+ try self.validateJsonResult(self.consumeRustString(onde_qwen3_32b_config()))
325
+ }
326
+
327
+ Function("qwen3_4bInstruct2507Config") { () -> String in
328
+ try self.validateJsonResult(
329
+ self.consumeRustString(onde_qwen3_4b_instruct_2507_config()))
330
+ }
331
+
332
+ Function("qwen3_4bThinking2507Config") { () -> String in
333
+ try self.validateJsonResult(
334
+ self.consumeRustString(onde_qwen3_4b_thinking_2507_config()))
335
+ }
336
+
337
+ Function("qwen3_30bA3bInstruct2507Config") { () -> String in
338
+ try self.validateJsonResult(
339
+ self.consumeRustString(onde_qwen3_30b_a3b_instruct_2507_config()))
340
+ }
341
+
342
Function("defaultSamplingConfig") { () -> String in
343
let result = self.consumeRustString(onde_default_sampling_config())
344
return try self.validateJsonResult(result)
sdk/react-native/package-lock.json
+2
-2
@@ -1,12 +1,12 @@
1
{
2
"name": "@ondeinference/react-native",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
"lockfileVersion": 3,
5
"requires": true,
6
"packages": {
7
"": {
8
"name": "@ondeinference/react-native",
9
- "version": "1.1.1",
9
+ "version": "1.1.2",
10
"license": "MIT OR Apache-2.0",
11
"devDependencies": {
12
"expo-modules-core": "^2.0.0",
sdk/react-native/rust/src/lib.rs
+96
-21
@@ -1,3 +1,6 @@
1
+// Copyright 2026 Onde Inference (Splitfire AB). All rights reserved.
2
+// Use of this source code is governed by the MIT license.
3
+//
4
//! C FFI bridge for the Onde React Native npm SDK.
5
//!
6
//! This crate wraps [`onde::inference::engine::ChatEngine`] with `extern "C"`
@@ -615,7 +618,7 @@ mod android {
618
// ── Engine lifecycle ─────────────────────────────────────────────────
619
620
#[no_mangle]
618
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeEngineCreate(
621
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineCreate(
622
_env: JNIEnv,
623
_class: JClass,
624
) -> jlong {
@@ -623,7 +626,7 @@ mod android {
626
}
627
628
#[no_mangle]
626
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeEngineDestroy(
629
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineDestroy(
630
_env: JNIEnv,
631
_class: JClass,
632
engine: jlong,
@@ -632,7 +635,7 @@ mod android {
635
}
636
637
#[no_mangle]
635
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeEngineLoadDefaultModel(
638
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineLoadDefaultModel(
639
mut env: JNIEnv,
640
_class: JClass,
641
engine: jlong,
@@ -657,7 +660,7 @@ mod android {
660
}
661
662
#[no_mangle]
660
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeEngineLoadModel(
663
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineLoadModel(
664
mut env: JNIEnv,
665
_class: JClass,
666
engine: jlong,
@@ -688,7 +691,7 @@ mod android {
691
}
692
693
#[no_mangle]
691
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeEngineUnloadModel(
694
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineUnloadModel(
695
mut env: JNIEnv,
696
_class: JClass,
697
engine: jlong,
@@ -698,7 +701,7 @@ mod android {
701
}
702
703
#[no_mangle]
701
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeEngineIsLoaded(
704
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineIsLoaded(
705
_env: JNIEnv,
706
_class: JClass,
707
engine: jlong,
@@ -713,7 +716,7 @@ mod android {
716
// ── Engine info ──────────────────────────────────────────────────────
717
718
#[no_mangle]
716
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeEngineInfo(
719
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineInfo(
720
mut env: JNIEnv,
721
_class: JClass,
722
engine: jlong,
@@ -725,7 +728,7 @@ mod android {
728
// ── System prompt ────────────────────────────────────────────────────
729
730
#[no_mangle]
728
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeEngineSetSystemPrompt(
731
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineSetSystemPrompt(
732
mut env: JNIEnv,
733
_class: JClass,
734
engine: jlong,
@@ -740,7 +743,7 @@ mod android {
743
}
744
745
#[no_mangle]
743
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeEngineClearSystemPrompt(
746
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineClearSystemPrompt(
747
_env: JNIEnv,
748
_class: JClass,
749
engine: jlong,
@@ -751,7 +754,7 @@ mod android {
754
// ── Sampling ─────────────────────────────────────────────────────────
755
756
#[no_mangle]
754
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeEngineSetSampling(
757
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineSetSampling(
758
mut env: JNIEnv,
759
_class: JClass,
760
engine: jlong,
@@ -768,7 +771,7 @@ mod android {
771
// ── History ──────────────────────────────────────────────────────────
772
773
#[no_mangle]
771
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeEngineHistory(
774
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineHistory(
775
mut env: JNIEnv,
776
_class: JClass,
777
engine: jlong,
@@ -778,7 +781,7 @@ mod android {
781
}
782
783
#[no_mangle]
781
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeEngineClearHistory(
784
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineClearHistory(
785
_env: JNIEnv,
786
_class: JClass,
787
engine: jlong,
@@ -787,7 +790,7 @@ mod android {
790
}
791
792
#[no_mangle]
790
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeEnginePushHistory(
793
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEnginePushHistory(
794
mut env: JNIEnv,
795
_class: JClass,
796
engine: jlong,
@@ -804,7 +807,7 @@ mod android {
807
// ── Inference ────────────────────────────────────────────────────────
808
809
#[no_mangle]
807
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeEngineSendMessage(
810
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineSendMessage(
811
mut env: JNIEnv,
812
_class: JClass,
813
engine: jlong,
@@ -820,7 +823,7 @@ mod android {
823
}
824
825
#[no_mangle]
823
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeEngineGenerate(
826
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeEngineGenerate(
827
mut env: JNIEnv,
828
_class: JClass,
829
engine: jlong,
@@ -846,7 +849,7 @@ mod android {
849
// ── Model config presets ─────────────────────────────────────────────
850
851
#[no_mangle]
849
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeDefaultModelConfig(
852
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeDefaultModelConfig(
853
mut env: JNIEnv,
854
_class: JClass,
855
) -> jstring {
@@ -855,7 +858,7 @@ mod android {
858
}
859
860
#[no_mangle]
858
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeQwen251_5bConfig(
861
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen251_15bConfig(
862
mut env: JNIEnv,
863
_class: JClass,
864
) -> jstring {
@@ -864,7 +867,7 @@ mod android {
867
}
868
869
#[no_mangle]
867
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeQwen253bConfig(
870
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen253bConfig(
871
mut env: JNIEnv,
872
_class: JClass,
873
) -> jstring {
@@ -872,10 +875,82 @@ mod android {
875
cstring_ptr_to_jstring(&mut env, result)
876
}
877
878
+ #[no_mangle]
879
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_10_16bConfig(
880
+ mut env: JNIEnv,
881
+ _class: JClass,
882
+ ) -> jstring {
883
+ cstring_ptr_to_jstring(&mut env, onde_qwen3_0_6b_config())
884
+ }
885
+
886
+ #[no_mangle]
887
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_11_17bConfig(
888
+ mut env: JNIEnv,
889
+ _class: JClass,
890
+ ) -> jstring {
891
+ cstring_ptr_to_jstring(&mut env, onde_qwen3_1_7b_config())
892
+ }
893
+
894
+ #[no_mangle]
895
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_14bConfig(
896
+ mut env: JNIEnv,
897
+ _class: JClass,
898
+ ) -> jstring {
899
+ cstring_ptr_to_jstring(&mut env, onde_qwen3_4b_config())
900
+ }
901
+
902
+ #[no_mangle]
903
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_18bConfig(
904
+ mut env: JNIEnv,
905
+ _class: JClass,
906
+ ) -> jstring {
907
+ cstring_ptr_to_jstring(&mut env, onde_qwen3_8b_config())
908
+ }
909
+
910
+ #[no_mangle]
911
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_114bConfig(
912
+ mut env: JNIEnv,
913
+ _class: JClass,
914
+ ) -> jstring {
915
+ cstring_ptr_to_jstring(&mut env, onde_qwen3_14b_config())
916
+ }
917
+
918
+ #[no_mangle]
919
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_132bConfig(
920
+ mut env: JNIEnv,
921
+ _class: JClass,
922
+ ) -> jstring {
923
+ cstring_ptr_to_jstring(&mut env, onde_qwen3_32b_config())
924
+ }
925
+
926
+ #[no_mangle]
927
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_14bInstruct2507Config(
928
+ mut env: JNIEnv,
929
+ _class: JClass,
930
+ ) -> jstring {
931
+ cstring_ptr_to_jstring(&mut env, onde_qwen3_4b_instruct_2507_config())
932
+ }
933
+
934
+ #[no_mangle]
935
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_14bThinking2507Config(
936
+ mut env: JNIEnv,
937
+ _class: JClass,
938
+ ) -> jstring {
939
+ cstring_ptr_to_jstring(&mut env, onde_qwen3_4b_thinking_2507_config())
940
+ }
941
+
942
+ #[no_mangle]
943
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeQwen3_130bA3bInstruct2507Config(
944
+ mut env: JNIEnv,
945
+ _class: JClass,
946
+ ) -> jstring {
947
+ cstring_ptr_to_jstring(&mut env, onde_qwen3_30b_a3b_instruct_2507_config())
948
+ }
949
+
950
// ── Sampling presets ─────────────────────────────────────────────────
951
952
#[no_mangle]
878
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeDefaultSamplingConfig(
953
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeDefaultSamplingConfig(
954
mut env: JNIEnv,
955
_class: JClass,
956
) -> jstring {
@@ -884,7 +959,7 @@ mod android {
959
}
960
961
#[no_mangle]
887
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeDeterministicSamplingConfig(
962
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeDeterministicSamplingConfig(
963
mut env: JNIEnv,
964
_class: JClass,
965
) -> jstring {
@@ -893,7 +968,7 @@ mod android {
968
}
969
970
#[no_mangle]
896
- pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_ondeMobileSamplingConfig(
971
+ pub extern "system" fn Java_com_ondeinference_OndeInferenceModule_nativeMobileSamplingConfig(
972
mut env: JNIEnv,
973
_class: JClass,
974
) -> jstring {
sdk/react-native/src/OndeInferenceModule.ts
+12
@@ -1,3 +1,6 @@
1
+// Copyright 2026 Onde Inference (Splitfire AB). All rights reserved.
2
+// Use of this source code is governed by the MIT license.
3
+//
4
import { requireNativeModule } from "expo-modules-core";
5
6
/**
@@ -42,6 +45,15 @@ export interface OndeInferenceNativeModule {
45
defaultModelConfig(): string;
46
qwen251_5bConfig(): string;
47
qwen253bConfig(): string;
48
+ qwen3_0_6bConfig(): string;
49
+ qwen3_1_7bConfig(): string;
50
+ qwen3_4bConfig(): string;
51
+ qwen3_8bConfig(): string;
52
+ qwen3_14bConfig(): string;
53
+ qwen3_32bConfig(): string;
54
+ qwen3_4bInstruct2507Config(): string;
55
+ qwen3_4bThinking2507Config(): string;
56
+ qwen3_30bA3bInstruct2507Config(): string;
57
defaultSamplingConfig(): string;
58
deterministicSamplingConfig(): string;
59
mobileSamplingConfig(): string;
sdk/react-native/src/index.ts
+48
@@ -1,3 +1,6 @@
1
+// Copyright 2026 Onde Inference (Splitfire AB). All rights reserved.
2
+// Use of this source code is governed by the MIT license.
3
+//
4
import OndeInferenceModule from "./OndeInferenceModule";
5
import type {
6
ChatMessage,
@@ -318,6 +321,51 @@ export function qwen253bConfig(): GgufModelConfig {
321
return parseModelConfig(OndeInferenceModule.qwen253bConfig());
322
}
323
324
+/** Return the Qwen 3 0.6B config (~0.5 GB). */
325
+export function qwen3_0_6bConfig(): GgufModelConfig {
326
+ return parseModelConfig(OndeInferenceModule.qwen3_0_6bConfig());
327
+}
328
+
329
+/** Return the Qwen 3 1.7B config (~1.3 GB). */
330
+export function qwen3_1_7bConfig(): GgufModelConfig {
331
+ return parseModelConfig(OndeInferenceModule.qwen3_1_7bConfig());
332
+}
333
+
334
+/** Return the Qwen 3 4B config (~2.5 GB). */
335
+export function qwen3_4bConfig(): GgufModelConfig {
336
+ return parseModelConfig(OndeInferenceModule.qwen3_4bConfig());
337
+}
338
+
339
+/** Return the Qwen 3 8B config (~5 GB). */
340
+export function qwen3_8bConfig(): GgufModelConfig {
341
+ return parseModelConfig(OndeInferenceModule.qwen3_8bConfig());
342
+}
343
+
344
+/** Return the Qwen 3 14B config (~8.4 GB). */
345
+export function qwen3_14bConfig(): GgufModelConfig {
346
+ return parseModelConfig(OndeInferenceModule.qwen3_14bConfig());
347
+}
348
+
349
+/** Return the Qwen 3 32B config (~19.8 GB). */
350
+export function qwen3_32bConfig(): GgufModelConfig {
351
+ return parseModelConfig(OndeInferenceModule.qwen3_32bConfig());
352
+}
353
+
354
+/** Return the non-thinking Qwen 3 4B Instruct 2507 config (~2.5 GB). */
355
+export function qwen3_4bInstruct2507Config(): GgufModelConfig {
356
+ return parseModelConfig(OndeInferenceModule.qwen3_4bInstruct2507Config());
357
+}
358
+
359
+/** Return the Qwen 3 4B Thinking 2507 config (~2.5 GB). */
360
+export function qwen3_4bThinking2507Config(): GgufModelConfig {
361
+ return parseModelConfig(OndeInferenceModule.qwen3_4bThinking2507Config());
362
+}
363
+
364
+/** Return the Qwen 3 30B-A3B Instruct 2507 MoE config (~18.6 GB). */
365
+export function qwen3_30bA3bInstruct2507Config(): GgufModelConfig {
366
+ return parseModelConfig(OndeInferenceModule.qwen3_30bA3bInstruct2507Config());
367
+}
368
+
369
/** Return default sampling parameters (temp=0.7, top_p=0.95, max_tokens=512). */
370
export function defaultSamplingConfig(): SamplingConfig {
371
return parseSamplingConfig(OndeInferenceModule.defaultSamplingConfig());
src/hf_cache.rs
+51
-63
@@ -1,3 +1,6 @@
1
+// Copyright 2026 Onde Inference (Splitfire AB). All rights reserved.
2
+// Use of this source code is governed by the MIT license.
3
+//
4
//! HuggingFace hub cache inspection, repair, and model download.
5
//!
6
//! This module owns all on-disk state management for locally cached
@@ -252,7 +255,9 @@ fn list_revisions(model_dir: &Path) -> Vec<String> {
255
target_os = "tvos",
256
target_os = "visionos",
257
target_os = "watchos",
255
- target_os = "android"
258
+ target_os = "android",
259
+ target_os = "windows",
260
+ target_os = "linux"
261
))]
262
fn make_progress(model_id: &str, downloaded: u64, total: u64, done: bool) -> ModelDownloadProgress {
263
let progress = if total > 0 {
@@ -990,7 +995,9 @@ pub fn list_supported_hf_models() -> SupportedHfModelsResponse {
995
target_os = "ios",
996
target_os = "tvos",
997
target_os = "visionos",
993
- target_os = "watchos"
998
+ target_os = "watchos",
999
+ target_os = "windows",
1000
+ target_os = "linux"
1001
))]
1002
pub async fn download_model<F>(
1003
model_id: String,
@@ -1000,11 +1007,8 @@ pub async fn download_model<F>(
1007
where
1008
F: Fn(ModelDownloadProgress) + Send + Sync + Clone + 'static,
1009
{
1003
- use crate::inference::models::{
1004
- BARTOWSKI_QWEN25_1_5B_INSTRUCT_GGUF, BARTOWSKI_QWEN25_3B_INSTRUCT_GGUF,
1005
- QWEN25_1_5B_GGUF_FILE, QWEN25_3B_GGUF_FILE,
1006
- };
1010
use crate::inference::token::hf_token_source;
1011
+ use crate::inference::GgufModelConfig;
1012
use mistralrs::GgufModelBuilder;
1013
use std::sync::{
1014
atomic::{AtomicBool, Ordering},
@@ -1036,6 +1040,9 @@ where
1040
return Err(format!("Model {} is not supported.", model_id));
1041
}
1042
1043
+ let config = GgufModelConfig::from_supported_model_id(&model_id)
1044
+ .ok_or_else(|| format!("Model {model_id} has no download configuration."))?;
1045
+
1046
let expected_size = SUPPORTED_MODEL_INFO
1047
.iter()
1048
.find(|m| m.id == model_id)
@@ -1055,14 +1062,8 @@ where
1062
// Recover from interrupted downloads where the blob exists but the
1063
// snapshot entry was never created (process killed after download,
1064
// before hf-hub finalised the cache structure).
1058
- let expected_files: Vec<&str> = match model_id.as_str() {
1059
- BARTOWSKI_QWEN25_1_5B_INSTRUCT_GGUF => vec![QWEN25_1_5B_GGUF_FILE],
1060
- BARTOWSKI_QWEN25_3B_INSTRUCT_GGUF => vec![QWEN25_3B_GGUF_FILE],
1061
- _ => vec![],
1062
- };
1063
- if !expected_files.is_empty() {
1064
- ensure_snapshot_entries(&model_id, &expected_files);
1065
- }
1065
+ let expected_files: Vec<&str> = config.files.iter().map(String::as_str).collect();
1066
+ ensure_snapshot_entries(&model_id, &expected_files);
1067
1068
// Emit an initial progress event based on the actual cache size so that
1069
// resumed downloads don't visually restart from 0%.
@@ -1077,6 +1078,12 @@ where
1078
false,
1079
));
1080
1081
+ let mut builder =
1082
+ GgufModelBuilder::new(&model_id, config.files.clone()).with_token_source(hf_token_source());
1083
+ if let Some(chat_template) = config.chat_template {
1084
+ builder = builder.with_chat_template(chat_template);
1085
+ }
1086
+
1087
// Shared flag so the monitor task knows when to stop.
1088
let finished = Arc::new(AtomicBool::new(false));
1089
let finished_clone = Arc::clone(&finished);
@@ -1109,29 +1116,14 @@ where
1116
));
1117
});
1118
1112
- // Build (and immediately drop) the model using the GGUF chat builder.
1113
- // Building triggers the HuggingFace hub download.
1114
- let result: Result<mistralrs::Model, anyhow::Error> = match model_id.as_str() {
1115
- BARTOWSKI_QWEN25_1_5B_INSTRUCT_GGUF => {
1116
- GgufModelBuilder::new(&model_id, vec![QWEN25_1_5B_GGUF_FILE])
1117
- .with_token_source(hf_token_source())
1118
- .build()
1119
- .await
1120
- }
1121
- BARTOWSKI_QWEN25_3B_INSTRUCT_GGUF => {
1122
- GgufModelBuilder::new(&model_id, vec![QWEN25_3B_GGUF_FILE])
1123
- .with_token_source(hf_token_source())
1124
- .build()
1125
- .await
1126
- }
1127
- _ => Err(anyhow::anyhow!(
1128
- "Model '{}' is not part of the chat-only release.",
1129
- model_id
1130
- )),
1131
- };
1119
+ // Building triggers the HuggingFace download and also validates that the
1120
+ // downloaded GGUF can be opened by the same backend used for inference.
1121
+ let result = builder.build().await;
1122
1123
finished.store(true, Ordering::Relaxed);
1134
- let _ = monitor_handle.join();
1124
+ if monitor_handle.join().is_err() {
1125
+ warn!("Download progress monitor panicked for model {model_id}");
1126
+ }
1127
1128
match result {
1129
Ok(_model) => {
@@ -1171,12 +1163,8 @@ pub async fn download_model<F>(
1163
where
1164
F: Fn(ModelDownloadProgress) + Send + Sync + Clone + 'static,
1165
{
1174
- use crate::inference::models::{
1175
- BARTOWSKI_QWEN25_1_5B_INSTRUCT_GGUF, BARTOWSKI_QWEN25_3B_INSTRUCT_GGUF,
1176
- QWEN25_1_5B_GGUF_FILE, QWEN25_1_5B_TOK_MODEL_ID, QWEN25_3B_GGUF_FILE,
1177
- QWEN25_3B_TOK_MODEL_ID,
1178
- };
1166
use crate::inference::token::hf_token_source;
1167
+ use crate::inference::GgufModelConfig;
1168
use hf_hub::Cache;
1169
use mistralrs::GgufModelBuilder;
1170
use mistralrs_core::GLOBAL_HF_CACHE;
@@ -1186,21 +1174,12 @@ where
1174
};
1175
use std::time::Duration;
1176
1189
- let (gguf_file, tok_model_id) = match model_id.as_str() {
1190
- id if id == BARTOWSKI_QWEN25_1_5B_INSTRUCT_GGUF => {
1191
- (QWEN25_1_5B_GGUF_FILE, QWEN25_1_5B_TOK_MODEL_ID)
1192
- }
1193
- id if id == BARTOWSKI_QWEN25_3B_INSTRUCT_GGUF => {
1194
- (QWEN25_3B_GGUF_FILE, QWEN25_3B_TOK_MODEL_ID)
1195
- }
1196
- _ => {
1197
- return Err(format!(
1198
- "Model '{}' is not supported on Android. \
1199
- Only Qwen 2.5 1.5B or 3B (GGUF) are available.",
1200
- model_id
1201
- ));
1202
- }
1203
- };
1177
+ let config = GgufModelConfig::from_supported_model_id(&model_id)
1178
+ .ok_or_else(|| format!("Model {model_id} has no Android download configuration."))?;
1179
+ let tok_model_id = config
1180
+ .tok_model_id
1181
+ .clone()
1182
+ .ok_or_else(|| format!("Model {model_id} has no Android tokenizer configuration."))?;
1183
1184
// ── Resolve the HF cache path ─────────────────────────────────────────
1185
//
@@ -1240,6 +1219,8 @@ where
1219
);
1220
1221
clean_stale_lock_files(&model_id);
1222
+ let expected_files: Vec<&str> = config.files.iter().map(String::as_str).collect();
1223
+ ensure_snapshot_entries(&model_id, &expected_files);
1224
ensure_model_cache_dir(&model_id);
1225
1226
let initial_bytes = model_cache_path(&model_id)
@@ -1253,6 +1234,14 @@ where
1234
false,
1235
));
1236
1237
+ let mut builder = GgufModelBuilder::new(&model_id, config.files.clone())
1238
+ .with_tok_model_id(&tok_model_id)
1239
+ .with_token_source(hf_token_source())
1240
+ .with_logging();
1241
+ if let Some(chat_template) = config.chat_template {
1242
+ builder = builder.with_chat_template(chat_template);
1243
+ }
1244
+
1245
// Native monitor thread — same reasoning as the Apple implementation.
1246
let finished = Arc::new(AtomicBool::new(false));
1247
let finished_clone = Arc::clone(&finished);
@@ -1276,15 +1265,12 @@ where
1265
));
1266
});
1267
1279
- let result = GgufModelBuilder::new(&model_id, vec![gguf_file])
1280
- .with_tok_model_id(tok_model_id)
1281
- .with_token_source(hf_token_source())
1282
- .with_logging()
1283
- .build()
1284
- .await;
1268
+ let result = builder.build().await;
1269
1270
finished.store(true, Ordering::Relaxed);
1287
- let _ = monitor_handle.join();
1271
+ if monitor_handle.join().is_err() {
1272
+ warn!("Download progress monitor panicked for model {model_id}");
1273
+ }
1274
1275
match result {
1276
Ok(_model) => {
@@ -1312,7 +1298,9 @@ where
1298
target_os = "tvos",
1299
target_os = "visionos",
1300
target_os = "watchos",
1315
- target_os = "android"
1301
+ target_os = "android",
1302
+ target_os = "windows",
1303
+ target_os = "linux"
1304
)))]
1305
pub async fn download_model<F>(
1306
model_id: String,
src/inference/engine.rs
+71
-20
@@ -1,3 +1,6 @@
1
+// Copyright 2026 Onde Inference (Splitfire AB). All rights reserved.
2
+// Use of this source code is governed by the MIT license.
3
+//
4
//! On-device LLM chat inference engine powered by [mistral.rs](https://github.com/EricLBuehler/mistral.rs).
5
//!
6
//! `ChatEngine` provides a high-level, framework-agnostic API for:
@@ -429,25 +432,9 @@ impl ChatEngine {
432
}
433
434
// Some older GGUF files (e.g. TheBloke) do not embed a chat template.
432
- // When the config provides one, write it to a temporary .jinja file
433
- // and pass the path — mistral.rs only accepts file paths ending in
434
- // .json or .jinja, not literal template strings.
435
- let _chat_template_tempfile = if let Some(ref template) = config.chat_template {
436
- let tmp_dir = std::env::temp_dir().join("onde-chat-templates");
437
- std::fs::create_dir_all(&tmp_dir).ok();
438
- let tmp_path = tmp_dir.join("chat_template.jinja");
439
- std::fs::write(&tmp_path, template).map_err(|e| InferenceError::ModelBuild {
440
- reason: format!(
441
- "Failed to write chat template to {}: {}",
442
- tmp_path.display(),
443
- e
444
- ),
445
- })?;
446
- builder = builder.with_chat_template(tmp_path.to_string_lossy().to_string());
447
- Some(tmp_path)
448
- } else {
449
- None
450
- };
435
+ if let Some(ref template) = config.chat_template {
436
+ builder = builder.with_chat_template(template);
437
+ }
438
439
let model = builder
440
.build()
@@ -459,7 +446,9 @@ impl ChatEngine {
446
let elapsed = start.elapsed();
447
448
let sampling = sampling.unwrap_or_else(|| {
462
- if cfg!(any(
449
+ if config.uses_extended_thinking() {
450
+ SamplingConfig::reasoning()
451
+ } else if cfg!(any(
452
target_os = "ios",
453
target_os = "tvos",
454
target_os = "visionos",
@@ -1921,6 +1910,52 @@ const DEEPSEEK_CODER_CHAT_TEMPLATE: &str = include_str!("deepseek_coder_chat_tem
1910
/// These mirror the constants in [`super::models`] but return a ready-to-use
1911
/// [`GgufModelConfig`].
1912
impl GgufModelConfig {
1913
+ /// Whether the model's default chat template emits an extended reasoning
1914
+ /// block that needs a larger output budget than the general presets.
1915
+ fn uses_extended_thinking(&self) -> bool {
1916
+ matches!(
1917
+ self.model_id.as_str(),
1918
+ super::models::BARTOWSKI_QWEN3_0_6B_GGUF
1919
+ | super::models::BARTOWSKI_QWEN3_1_7B_GGUF
1920
+ | super::models::BARTOWSKI_QWEN3_4B_GGUF
1921
+ | super::models::BARTOWSKI_QWEN3_8B_GGUF
1922
+ | super::models::BARTOWSKI_QWEN3_14B_GGUF
1923
+ | super::models::BARTOWSKI_QWEN3_32B_GGUF
1924
+ | super::models::BARTOWSKI_QWEN3_4B_THINKING_2507_GGUF
1925
+ )
1926
+ }
1927
+
1928
+ /// Resolve a model-management ID to the same configuration used by the
1929
+ /// inference engine. Keeping this mapping in one place prevents download
1930
+ /// and load support from drifting apart.
1931
+ pub(crate) fn from_supported_model_id(model_id: &str) -> Option<Self> {
1932
+ match model_id {
1933
+ super::models::BARTOWSKI_QWEN25_0_5B_INSTRUCT_GGUF => Some(Self::qwen25_0_5b()),
1934
+ super::models::BARTOWSKI_QWEN25_1_5B_INSTRUCT_GGUF => Some(Self::qwen25_1_5b()),
1935
+ super::models::BARTOWSKI_QWEN25_3B_INSTRUCT_GGUF => Some(Self::qwen25_3b()),
1936
+ super::models::BARTOWSKI_QWEN3_0_6B_GGUF => Some(Self::qwen3_0_6b()),
1937
+ super::models::BARTOWSKI_QWEN3_1_7B_GGUF => Some(Self::qwen3_1_7b()),
1938
+ super::models::BARTOWSKI_QWEN3_4B_GGUF => Some(Self::qwen3_4b()),
1939
+ super::models::BARTOWSKI_QWEN3_8B_GGUF => Some(Self::qwen3_8b()),
1940
+ super::models::BARTOWSKI_QWEN3_14B_GGUF => Some(Self::qwen3_14b()),
1941
+ super::models::BARTOWSKI_QWEN3_32B_GGUF => Some(Self::qwen3_32b()),
1942
+ super::models::BARTOWSKI_QWEN3_4B_INSTRUCT_2507_GGUF => {
1943
+ Some(Self::qwen3_4b_instruct_2507())
1944
+ }
1945
+ super::models::BARTOWSKI_QWEN3_4B_THINKING_2507_GGUF => {
1946
+ Some(Self::qwen3_4b_thinking_2507())
1947
+ }
1948
+ super::models::BARTOWSKI_QWEN3_30B_A3B_INSTRUCT_2507_GGUF => {
1949
+ Some(Self::qwen3_30b_a3b_instruct_2507())
1950
+ }
1951
+ super::models::BARTOWSKI_QWEN25_CODER_7B_INSTRUCT_GGUF => Some(Self::qwen25_coder_7b()),
1952
+ super::models::THEBLOKE_DEEPSEEK_CODER_6_7B_INSTRUCT_GGUF => {
1953
+ Some(Self::deepseek_coder_6_7b())
1954
+ }
1955
+ _ => None,
1956
+ }
1957
+ }
1958
+
1959
/// Qwen 2.5 0.5B Instruct (GGUF Q4_K_M), ~380 MB.
1960
///
1961
/// Smallest option. Used on tvOS (Apple TV) where the memory limit is 2 GB.
@@ -2317,6 +2352,22 @@ mod tests {
2352
}
2353
}
2354
2355
+ #[test]
2356
+ fn every_supported_model_resolves_to_download_config() {
2357
+ for model_id in super::super::models::SUPPORTED_MODELS {
2358
+ let config = GgufModelConfig::from_supported_model_id(model_id)
2359
+ .unwrap_or_else(|| panic!("missing config for {model_id}"));
2360
+ assert_eq!(config.model_id, *model_id);
2361
+ assert_eq!(config.files.len(), 1);
2362
+ }
2363
+ }
2364
+
2365
+ #[test]
2366
+ fn qwen3_thinking_models_request_extended_sampling() {
2367
+ assert!(GgufModelConfig::qwen3_4b_thinking_2507().uses_extended_thinking());
2368
+ assert!(!GgufModelConfig::qwen3_4b_instruct_2507().uses_extended_thinking());
2369
+ }
2370
+
2371
#[test]
2372
fn gguf_model_config_platform_default() {
2373
let cfg = GgufModelConfig::platform_default();
src/inference/models.rs
+15
-12
@@ -1,3 +1,6 @@
1
+// Copyright 2026 Onde Inference (Splitfire AB). All rights reserved.
2
+// Use of this source code is governed by the MIT license.
3
+//
4
/// Pre-quantized Qwen 2.5 1.5B Instruct (GGUF Q4_K_M) — lightest mobile option (~941 MB).
5
/// Fits comfortably on both iOS (iPhone 16e, 8 GB RAM) and Android memory-constrained devices.
6
/// Note: the 3B variant (~1.93 GB) caused OOM on iPhone 16e because iOS gives apps only ~2-3 GB;
@@ -287,8 +290,8 @@ pub const SUPPORTED_MODEL_INFO: &[SupportedModelInfo] = &[
290
org: "Qwen / Alibaba",
291
description: "Smallest Qwen 3 variant with tool calling (~0.5 GB). \
292
Suitable for tvOS and the most memory-constrained mobile devices.",
290
- // Approximate Q4_K_M size; HF API unreachable at authoring time.
291
- expected_size_bytes: 483_000_000,
293
+ // Exact Q4_K_M file size from HuggingFace API siblings[].size.
294
+ expected_size_bytes: 484_220_320,
295
},
296
SupportedModelInfo {
297
id: BARTOWSKI_QWEN3_1_7B_GGUF,
@@ -305,7 +308,7 @@ pub const SUPPORTED_MODEL_INFO: &[SupportedModelInfo] = &[
308
description: "Full tool-calling support with extended reasoning mode (~2.7 GB). \
309
Recommended for siGit Code on macOS, Linux, and Windows.",
310
// Qwen_Qwen3-4B-Q4_K_M.gguf from bartowski repo.
308
- expected_size_bytes: 2_596_306_912,
311
+ expected_size_bytes: 2_497_280_960,
312
},
313
SupportedModelInfo {
314
id: BARTOWSKI_QWEN3_8B_GGUF,
@@ -313,7 +316,7 @@ pub const SUPPORTED_MODEL_INFO: &[SupportedModelInfo] = &[
316
org: "Qwen / Alibaba",
317
description: "Strong tool-calling model with extended thinking (~5 GB). \
318
Best balance of quality and memory for macOS with 24+ GB RAM.",
316
- expected_size_bytes: 5_131_567_104,
319
+ expected_size_bytes: 5_027_784_224,
320
},
321
SupportedModelInfo {
322
id: BARTOWSKI_QWEN3_14B_GGUF,
@@ -331,8 +334,8 @@ pub const SUPPORTED_MODEL_INFO: &[SupportedModelInfo] = &[
334
org: "Qwen / Alibaba",
335
description: "Largest dense Qwen 3 model with extended thinking (~19.8 GB). \
336
Highest-quality dense variant; requires 32+ GB RAM.",
334
- // Approximate Q4_K_M size; HF API unreachable at authoring time.
335
- expected_size_bytes: 21_260_000_000,
337
+ // Exact Q4_K_M file size from HuggingFace API siblings[].size.
338
+ expected_size_bytes: 19_762_149_696,
339
},
340
SupportedModelInfo {
341
id: BARTOWSKI_QWEN3_4B_INSTRUCT_2507_GGUF,
@@ -340,8 +343,8 @@ pub const SUPPORTED_MODEL_INFO: &[SupportedModelInfo] = &[
343
org: "Qwen / Alibaba",
344
description: "Latest non-thinking 4B checkpoint — faster, predictable chat and \
345
tool use (~2.5 GB). Recommended general-purpose Qwen 3 model.",
343
- // Approximate Q4_K_M size; HF API unreachable at authoring time.
344
- expected_size_bytes: 2_500_000_000,
346
+ // Exact Q4_K_M file size from HuggingFace API siblings[].size.
347
+ expected_size_bytes: 2_497_280_736,
348
},
349
SupportedModelInfo {
350
id: BARTOWSKI_QWEN3_4B_THINKING_2507_GGUF,
@@ -349,8 +352,8 @@ pub const SUPPORTED_MODEL_INFO: &[SupportedModelInfo] = &[
352
org: "Qwen / Alibaba",
353
description: "Latest reasoning-focused 4B checkpoint with extended thinking (~2.5 GB). \
354
Stronger reasoning and tool-use accuracy; load with max_tokens ≥ 4096.",
352
- // Approximate Q4_K_M size; HF API unreachable at authoring time.
353
- expected_size_bytes: 2_500_000_000,
355
+ // Exact Q4_K_M file size from HuggingFace API siblings[].size.
356
+ expected_size_bytes: 2_497_280_736,
357
},
358
SupportedModelInfo {
359
id: BARTOWSKI_QWEN3_30B_A3B_INSTRUCT_2507_GGUF,
@@ -358,8 +361,8 @@ pub const SUPPORTED_MODEL_INFO: &[SupportedModelInfo] = &[
361
org: "Qwen / Alibaba",
362
description: "Flagship mixture-of-experts model: 30B total / ~3B active (~18.6 GB). \
363
Near-dense quality at far lower inference cost; requires 32+ GB RAM.",
361
- // Approximate Q4_K_M size; HF API unreachable at authoring time.
362
- expected_size_bytes: 19_971_600_000,
364
+ // Exact Q4_K_M file size from HuggingFace API siblings[].size.
365
+ expected_size_bytes: 18_632_183_808,
366
},
367
SupportedModelInfo {
368
id: BARTOWSKI_QWEN25_CODER_7B_INSTRUCT_GGUF,
src/inference/types.rs
+18
@@ -1,3 +1,6 @@
1
+// Copyright 2026 Onde Inference (Splitfire AB). All rights reserved.
2
+// Use of this source code is governed by the MIT license.
3
+//
4
//! Shared types for the on-device LLM inference engine.
5
//!
6
//! These types are intentionally framework-agnostic — they do **not** depend on
@@ -111,6 +114,15 @@ impl Default for SamplingConfig {
114
}
115
116
impl SamplingConfig {
117
+ /// Defaults for models that emit an extended reasoning block before the
118
+ /// user-visible answer.
119
+ pub fn reasoning() -> Self {
120
+ Self {
121
+ max_tokens: Some(4096),
122
+ ..Self::default()
123
+ }
124
+ }
125
+
126
/// Deterministic sampling (temperature = 0, greedy decoding).
127
pub fn deterministic() -> Self {
128
Self {
@@ -505,6 +517,12 @@ mod tests {
517
assert_eq!(cfg.max_tokens, Some(128));
518
}
519
520
+ #[test]
521
+ fn sampling_config_reasoning_has_extended_budget() {
522
+ let config = SamplingConfig::reasoning();
523
+ assert_eq!(config.max_tokens, Some(4096));
524
+ }
525
+
526
#[test]
527
fn engine_status_display() {
528
assert_eq!(EngineStatus::Ready.to_string(), "ready");