Skip to content

feat: expose apple intelligence availability reason - #225

Open
jaimebg wants to merge 1 commit into
callstackincubator:mainfrom
jaimebg:feat/apple-availability-status
Open

feat: expose apple intelligence availability reason#225
jaimebg wants to merge 1 commit into
callstackincubator:mainfrom
jaimebg:feat/apple-availability-status

Conversation

@jaimebg

@jaimebg jaimebg commented Jul 27, 2026

Copy link
Copy Markdown

Summary

isAvailable() collapses Apple's SystemLanguageModel.Availability into a plain boolean, so the reason Apple Intelligence is unavailable is lost at the bridge. Apps can't tell "this device can't run it" apart from "the user hasn't switched Apple Intelligence on" — which is a real UX loss, because the second one is fixable by the user and we currently can't tell them so. The model still downloading (modelNotReady) is likewise indistinguishable from a permanent failure.

Apple exposes the reason via .unavailable(reason); this PR stops throwing it away.

Changes

  • New getAvailability() on the provider (apple.getAvailability()), on AppleFoundationModels, and on the TurboModule spec. Returns a string union:

    Status Meaning Suggested UX
    available Model ready Proceed
    deviceNotEligible Hardware doesn't support Apple Intelligence Fall back to another provider
    appleIntelligenceNotEnabled User hasn't turned it on "Enable it in Settings"
    modelNotReady Enabled, still downloading "Try again later"
    unsupportedOS Older than iOS 26 Prompt to update
    unknown Reason this version doesn't know yet Generic fallback

    unsupportedOS matches the vocabulary already used by AppleLLMErrorCodes.UnsupportedOS; unknown keeps the wrapper forward-compatible if Apple adds reasons.

  • MODEL_UNAVAILABLE errors now carry the same detail, e.g. "Apple Intelligence model is not available: Apple Intelligence is not enabled in Settings" instead of one generic message, across generateText, generateStream and countTokens. The error code is unchanged.

  • AppleAvailability type exported from the package entrypoint.

  • Docs: new "Availability Status" section in apple/generating.md, plus pointers from running-on-simulator.md and the agent skill reference.

Not a breaking change

isAvailable(): boolean keeps its exact contract and stays in the native spec — it's now derived from getAvailability(), so the two can't drift. Every existing snippet in the docs and the example app keeps working; getAvailability() is purely additive for callers who want the reason.

Test plan

I don't have an Apple Intelligence-capable device to hand, so this hasn't been exercised on-device — the reason-specific branches in particular would benefit from a maintainer sanity check. What I did verify:

  • bun run lint and bun run typecheck (whole monorepo) pass.
  • bun run prepare in packages/apple-llm builds; emitted .d.ts exposes getAvailability(): AppleAvailability and the exported type.
  • AppleAvailability.swift, AppleLLMError.swift and AppleLLMImpl.swift pass swiftc -typecheck against the real iOS 26 SDK — including the switch over SystemLanguageModel.default.availability, which is exhaustive without needing @unknown default.
  • Ran RN codegen over the updated spec: the string-literal union is parsed as StringLiteralUnionTypeAnnotation and generates - (NSString *)getAvailability; with a StringKind sync host function, which is what AppleLLM.mm implements.

Suggested on-device checks: toggle Apple Intelligence off in Settings → expect appleIntelligenceNotEnabled; on an ineligible device → deviceNotEligible; right after enabling, before the download finishes → modelNotReady.

Possible follow-up

The example app's SetupAdapter.isAvailable() still maps to the 3-state 'yes' | 'no' | 'availableForDownload', so it doesn't surface the new reason in the UI. I left it alone to keep this PR to one logical change, but happy to add it here if you'd prefer the demo to show it off.

Notes

isAvailable() collapsed SystemLanguageModel.Availability into a boolean, so
apps could not tell an ineligible device apart from Apple Intelligence
simply being switched off, and had no way to point the user at Settings.

Add getAvailability() alongside it, returning the reason Apple already
exposes via .unavailable(reason). MODEL_UNAVAILABLE errors now carry the
same detail instead of a single generic message.

isAvailable() keeps its boolean contract and is now derived from
getAvailability().
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

@jaimebg is attempting to deploy a commit to the Callstack Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant