Unified LLM service access layer — one API to access 325 AI providers
Flutter/Dart wraps the Rust core through the aimux-ffi C ABI (via dart:ffi).
On pub.dev (publisher: arcships.ai):
flutter pub add aimuxThe package is a Flutter plugin: the Rust core ships inside it —
libaimux_ffi.so per ABI (Android) and aimux_ffi.xcframework (iOS) are
embedded at publish time, so no extra download or build step is needed.
iOS integrates via SwiftPM (default on Flutter 3.44+, ios/aimux/Package.swift)
or CocoaPods fallback (ios/aimux.podspec).
While developing against the repo, depend on the path:
dependencies:
aimux:
path: bindings/flutterDesktop (Linux/macOS/Windows) is supported for development and tests: the
library is resolved from the platform library path — build it once with
cargo build -p aimux-ffi --release and point LD_LIBRARY_PATH
(DYLD_LIBRARY_PATH on macOS) at target/release.
The binding loads the platform library at runtime
(libaimux_ffi.so / libaimux_ffi.dylib / aimux_ffi.dll) — ship it with
your app or place it where the loader can find it.
final model = Model.openai('sk-...', 'gpt-4o', baseUrl: 'http://localhost:3000');
final result = model.generateText('What is Rust?');
model.close();All 250 registry-backed OpenAI-compatible providers are reachable by name;
ProviderName holds the constants:
Scope:
provider(name)covers only the 250 registry OpenAI-compatible providers; Anthropic/Google/multimodal/local → typed factories (Model.anthropic(apiKey, modelId)); custom endpoints → base-URL variant. Full list: providers.md.
// 推荐:ProviderName.groq 常量(补全 + 防拼写错误)
final model = Model.provider(ProviderName.groq, 'llama-3.3-70b');
final result = model.generateText('Hello');
model.close();
// 字符串形式同样可用 + 可选 config JSON ({"base_url": "..."}):
final model2 = Model.provider('groq', 'llama-3.3-70b', apiKey: 'sk-...');
model2.close();Unknown names throw NoSuchProviderError (payload: the provider id); valid
names come from the generated ProviderName constants.
Engine and binding failures throw an AimuxException subclass hierarchy
(idiomatic Dart — is / on type checks, not stringly code switches):
Exception (implements)
└── AimuxException
├── UnknownError
├── JSONParseError / InvalidResponseDataError
├── ToolError
├── InvalidArgumentError / InvalidPromptError
├── TokenExpiredError // status 401
├── UnsupportedFunctionalityError
├── NoSuchModelError / NoSuchProviderError
├── APICallError // every HTTP-shaped failure; branch on status
├── AimuxTimeoutError
├── RequestAbortedError
└── OtherError
Every instance has message, code (AimuxErrorCode constants matching C
AimuxErrorCode), status (HTTP or -1), and retryMs (hint or -1;
0 = retry now). Built from the C AimuxError out-param via
AimuxException.fromC — constructors return uint64_t (0 = failure),
payload calls return char* (NULL = failure), streams return int32_t
(0 = failure). No JSON error-envelope sniffing on the main path.
import 'package:aimux/aimux.dart'; // exports errors.dart
try {
final result = model.generateText('hi');
} on APICallError catch (e) {
if (e.status == 429) {
// rate limited; e.retryMs is the hint
} else if (e.status == 401) {
// auth failure
}
} on AimuxException catch (e) {
// any engine / binding failure
}Local closed-handle checks still throw StateError (not AimuxException).
Stream terminal failures surface via Stream.addError(AimuxException) —
there is no on_error callback; provider mid-stream StreamPart::Error is
data on on_part.
final model = Model.openai('sk-...', 'gpt-4o');
final result = model.generateText('What is Rust?');
model.close();Parameters, return value, and the
raw.contentvariants are documented in the API overview.
// streaming
final model = Model.openai('sk-...', 'gpt-4o');
final stream = model.streamText('Write a haiku');
await for (final part in stream) {
if (part.containsKey('TextDelta')) print(part['TextDelta']['delta']);
}
model.close();Stream part variants are documented in the API overview.
The raw Model speaks JSON maps. TypedModel wraps it with typed objects:
final model = TypedModel(Model.openai('sk-...', 'gpt-4o'));
final result = model.generateText('What is Rust?');
print(result.text); // typed GenerateTextResult
final stream = model.streamText('Write a haiku');
await for (final part in stream) {
if (part is StreamPartTextDelta) print(part.delta); // typed StreamPart variants
}
model.close();| API | Signature | Description |
|---|---|---|
TypedModel |
TypedModel(Model raw) — wrap a raw Model |
|
generateText |
GenerateTextResult generateText(String prompt, [GenerateTextOptions? options]) |
String prompt |
generateTextMessages |
GenerateTextResult generateTextMessages(List<ModelMessage> messages, [GenerateTextOptions? options]) |
Multi-turn typed messages |
streamText |
Stream<StreamPart> streamText(Object prompt, [GenerateTextOptions? options]) |
Yields typed StreamParts |
close |
void close() |
Release the native handle |
bindings/flutter/lib/types.dart declares the typed model surface (with
toJson / fromJson on each): Role, FinishReasonUnified,
ReasoningEffort, TokenUsage, Usage, FinishReason, ToolCall,
FunctionTool, Tool, ToolChoice, ResponseMetadata, GenerateContent
(sealed), GenerateResult, GenerateTextResult, GenerateTextOptions,
ModelMessage, StreamPart (sealed), FileBytes, FileData, ContentPart.
Text generation and streaming are supported. Multimodal features (embedding, TTS, STT, image, video, rerank, search, files) are reachable only through the raw C ABI until the wrappers are extended — see the coverage matrix.