apple_foundation_models 0.5.0
apple_foundation_models: ^0.5.0 copied to clipboard
Run Apple's on-device language model from Flutter. Streaming, tool calling, and schema-constrained structured output — private, offline, and free.
0.5.0 #
What a session spends, what the model is, and moving a conversation between
models, the parts of iOS 27 and macOS 27 that can be checked on a Mac today.
All checked against the real model on macOS 27.0.1, in
example/integration_test/model_test.dart.
session.usage()returns aTokenUsage: input, cached input, output and reasoning tokens, as a running total. Subtract two readings for one request. The second request in a session reused 68 of its 82 input tokens from the cache. Needs iOS 27 or macOS 27 and Xcode 27; null elsewhere.AppleFoundationModels.modelInfo()returns aModelInfo: the model's name ("AFM 3 Core"), itsModelCapabilitys and its context size (4096). Both on-device models report vision, tool calling and guided generation, but not reasoning. Name and capabilities need iOS 27 or macOS 27; context size needs only Xcode 27. Unknown fields are null, not empty.AppleFoundationModels.tokenCount(text)measures text before you send it, so a long prompt can be cut down instead of failing withContextWindowExceededException. Needs iOS 26.4 or macOS 26.4 and Xcode 27.session.switchModel(useCase)moves the conversation to the other model, keeping the transcript, instructions and tools, andsession.useCasesays where it is. iOS 27's profiles can only be attached when a session is created, so the plugin rebuilds the native session from its transcript instead, which works on iOS 26 and macOS 26 too. Between requests only: mid-request it throwsConcurrentRequestException. On the real model a code word survived two switches, a tool still fired after them, andusagekept counting across them.
Additive only: nothing existing changed.
0.4.1 #
In an app built with Xcode 27, the framework's errors arrived as the wrong
exception. FoundationModels throws different types to apps built with Xcode
27 — LanguageModelError, SystemLanguageModel.Error,
LanguageModelSession.Error and GeneratedContent.ParsingError, in place of
the LanguageModelSession.GenerationError that only Xcode 26 builds still
get — and the plugin matched only the old type. So in an Xcode 27 build,
ContextWindowExceededException, GuardrailViolationException,
RefusalException, UnsupportedLanguageException, RateLimitedException,
AssetsUnavailableException and DecodingFailureException all reached Dart as
a generic FoundationModelsPlatformException. Both generations are now mapped,
so the exception you catch no longer depends on which Xcode built the app.
- Checked against the real model on macOS 27: a prompt past the context window
threw
FoundationModelsPlatformExceptionwith 0.4.0, and throwsContextWindowExceededExceptionnow, after which the session answers normally. The test isexample/integration_test/errors_test.dart. - Two errors Xcode 27 added have no type here yet. They arrive as
FoundationModelsPlatformExceptionwith itscodeset totimeoutortranscriptMutationWhileResponding. Content the model can't take, such as an image for a model that can't see, is anUnsupportedCapabilityException. - Building with Xcode 27 no longer warns that
GenerationOptions(sampling:)is deprecated.
No API changes.
0.4.0 #
Images in prompts. On iOS 27 and macOS 27 the model can look at pictures,
and every request method now takes images: PromptImage.bytes for anything
Core Image decodes, or PromptImage.file, which is read natively so a large
photo never crosses the channel. Each can carry a label the model can refer
to, and a photo's orientation is applied either way.
AppleFoundationModels.supportsImages()says whether they will work: iOS 27 or macOS 27, an app built with Xcode 27 or later, and a model that can see.- Where they will not, a request carrying them throws
UnsupportedCapabilityExceptionrather than quietly leaving them out, and an image that will not decode throwsInvalidImageException, naming which one. - Checked against the real model on macOS 27: red, blue and green squares read as red, blue and green — sent as bytes, as a file, in structured output and streamed. The example app gains a Look at a picture button.
- Use the general model for images. On macOS 27.0 the content-tagging model reports that it can see, then fails every image it is given.
The transcript learned the two things macOS 27 and iOS 27 added to it:
- Images used to vanish from their entry's text. They now read as
[image], or[image: label]. - The model's reasoning used to arrive as
TranscriptRole.unknownwith its text dropped. It is nowTranscriptRole.reasoning, text included — though the on-device model refuses reasoning on macOS 27.0 ("The selected model does not support reasoning"), so none appears from it yet.
Also carries 0.3.3's fix for a second request on a busy session crashing the app on macOS 27.
Breaking, for code that switches exhaustively: TranscriptRole gains
reasoning, and the sealed FoundationModelsException gains the two
subtypes above.
Built with Xcode 26, nothing else changes: those SDKs define none of this, so
the plugin compiles without it, supportsImages() answers false and such
entries read as unknown. With Xcode 27 the compiler's two "switch must be
exhaustive" warnings are gone.
0.3.3 #
A second request on a busy session can no longer take the app down on
macOS 27. The framework calls it a programmer error, and on macOS 27 it
corrupted its own heap on the way to saying so: two of three runs of the
overlapping-request test crashed the app inside FoundationModels, and the third
threw a generic exception instead of ConcurrentRequestException. The plugin
now turns the second request away itself, before the framework sees it, with
the ConcurrentRequestException this package has always documented.
No API changes. 0.4.0 carries the same fix.
0.3.2 #
Corrects 0.3.1, which claimed a floor it had not actually been checked against.
0.3.1 added package:meta so Bridge could carry @internal. That constraint
does not resolve on older SDKs, so the package would not have installed at the
floor its own changelog advertised — and the check that was supposed to catch
this reported success because the analyzer never ran at all. The verification
now proves the analyzer executed before trusting a zero error count.
@internal is gone, and with it the dependency. Bridge lives under lib/src/
and the barrel never exports it, so it was already unreachable; the annotation
bought nothing and cost a resolution failure.
Genuinely type-checked against Flutter 3.16.9 / Dart 3.2.6 this time, against a declared floor of Flutter 3.24.
0.3.1 #
No behaviour changes. sdk: ^3.13.0 meant Dart 3.13, which ships only with
Flutter 3.47 — released three weeks ago — so this package refused to resolve
for anyone on an older SDK. One null-aware element (Dart 3.8) was all that
required it; rewritten, the floor is now ^3.5.0 / Flutter 3.24 and verified by
type-checking against Flutter 3.16.9.
0.3.0 #
An audit of every package in this repo found four defects here.
Fixed #
- Two enum fields in one schema were rejected at runtime. The native side
mints one Swift type per node and named every unnamed enum
Choice, so a schema with two of them declared the same type twice — and a classification schema, which is what enums are for, usually has more than one. Unnamed objects collided the same way onResult. Names are minted uniquely when a schema is serialised, andSchema.oneOftakes anamewhen you want a particular one. ToolCallExceptionandSchemaExceptionwere unreachable for anything the framework raised. A tool that threw arrives wrapped inLanguageModelSession.ToolCallErrorwith the real error nested inside, and the payload builder only matched this plugin's own types — so every framework-raised failure collapsed into the generic fallback and lost the tool name with it. The wrapper is unwrapped first now.- The in-flight task dictionary was a data race. It was written from the platform thread when a request started and from the cooperative pool when it finished, with no synchronisation. On a Swift Dictionary that is heap corruption, not a wrong answer. Every access is on the main queue now.
- The README told you to install a version that excludes this one. The
install snippet pinned
^0.1.0, which resolves to>=0.1.0 <0.2.0. The 0.2.1 note claimed every snippet in the README was analyser-checked, which a YAML block escapes.
0.2.1 #
Documentation only — no API changes.
- README code blocks now parse. Several listed variants one per line without terminating semicolons, so copying a block gave a syntax error even though each individual line was fine. Every snippet in this README is now checked through the analyser.
0.2.0 #
Renamed LoadingStreamDeltas to ModelStreamDeltas. The old name was
copy-paste from a sibling package and had nothing to do with loading anything.
It matters more than a private misnomer would: this is an extension on
Stream<String>, so importing this package put that wrong name into the tools
of anyone with a string stream anywhere in their app.
Nothing about the behaviour changed, and deltas() is called the same way. You
would only notice if you had written the extension name explicitly, which is
rare — but it is a breaking change, so it gets a minor bump.
- The transcript API is documented now.
session.transcript()andTranscriptRolewere public and appeared in neither the README nor the example, so reading back what a session was told meant reading the source.
0.1.2 #
Packaging only — no API or behaviour changes.
- The demo animations now ship inside the package, so they appear as screenshots on the pub.dev page rather than only in the README on GitHub.
.pubignoreexcludes the raw recorder frames, so shipping them costs about 360 KB rather than the 11 MB the frame directory would have added.
0.1.1 #
Fixes a macOS build failure under Swift Package Manager.
Package.swift declared only .iOS("15.0") and no macOS platform. SPM defaults
an undeclared platform to macOS 10.13, where Swift concurrency's Task does not
exist, so the plugin failed to compile with 'Task' is only available in macOS 10.15 or newer. The podspec had always said osx 10.15, so CocoaPods builds
were unaffected and the two paths disagreed — and SPM is on by default on
current Flutter, so most macOS users would have hit this.
No API changes.
0.1.0 #
Initial release.
-
Generation guides:
Schema.integer(min:, max:),Schema.number(min:, max:),Schema.string(pattern:),Schema.array(exactItems:). Bounds constrain generation itself, so a 1-to-5 rating cannot come back as 47. -
respondInto/streamIntodecode straight into your own types. -
deltas()turns cumulative snapshots into increments. -
ModelUseCase.contentTaggingselects Apple's tagging-tuned model. -
AppleFoundationModels.supportedLanguages()andavailabilityChanges, the latter firing when the model finishes downloading mid-session. -
AppleFoundationModels.availability()with typed reasons —deviceNotEligible,appleIntelligenceNotEnabled,modelNotReady,osTooOld,unsupportedPlatform— each carrying an explanation and, where one exists, a remedy. -
LanguageModelSessionwith instructions, transcript,prewarm, andisResponding. -
Four response modes:
respond,respondAs(schema-constrained),stream, andstreamAs(structured output arriving progressively). -
A Dart schema DSL — objects, arrays, enums, optional fields — rebuilt natively as a
DynamicGenerationSchema, so typed output needs no codegen. -
Tool calling: the model invokes a Dart function mid-generation and waits for the result.
-
GenerationOptionswith temperature, token cap, and greedy / top-k / top-p sampling. -
Every framework error mapped to its own exception type.
-
Compiles and runs on every platform. Non-Apple targets report
unsupportedPlatforminstead of failing the build, and an Xcode without the iOS 26 SDK still compiles.
