Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions packages/apple-llm/ios/AppleAvailability.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// AppleAvailability.swift
// AppleLLM
//

import Foundation

/// Mirrors `SystemLanguageModel.Availability` so that the reason why Apple
/// Intelligence is unavailable survives the bridge to JavaScript.
enum AppleAvailability: String {
case available
case deviceNotEligible
case appleIntelligenceNotEnabled
case modelNotReady
case unsupportedOS
case unknown

/// Human readable explanation, used to give `MODEL_UNAVAILABLE` errors the
/// same level of detail as `getAvailability()`.
var unavailableDescription: String {
switch self {
case .available:
return "Apple Intelligence model is available"
case .deviceNotEligible:
return "Apple Intelligence model is not available: this device is not eligible for Apple Intelligence"
case .appleIntelligenceNotEnabled:
return "Apple Intelligence model is not available: Apple Intelligence is not enabled in Settings"
case .modelNotReady:
return "Apple Intelligence model is not available: the model is not ready yet, it may still be downloading"
case .unsupportedOS:
return "Apple Intelligence not available on this iOS version"
case .unknown:
return "Apple Intelligence model is not available"
}
}
}
4 changes: 4 additions & 0 deletions packages/apple-llm/ios/AppleLLM.mm
Original file line number Diff line number Diff line change
Expand Up @@ -174,5 +174,9 @@ - (nonnull NSNumber *)isAvailable {
return @([_llm isAvailable]);
}

- (nonnull NSString *)getAvailability {
return [_llm getAvailability];
}


@end
6 changes: 3 additions & 3 deletions packages/apple-llm/ios/AppleLLMError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

enum AppleLLMError: Error, LocalizedError {
case modelUnavailable
case modelUnavailable(AppleAvailability)
case unsupportedOS
case generationError(String)
case streamNotFound(String)
Expand All @@ -21,8 +21,8 @@ enum AppleLLMError: Error, LocalizedError {

var errorDescription: String? {
switch self {
case .modelUnavailable:
return "Apple Intelligence model is not available"
case .modelUnavailable(let availability):
return availability.unavailableDescription
case .unsupportedOS:
return "Apple Intelligence not available on this iOS version"
case .generationError(let message):
Expand Down
45 changes: 34 additions & 11 deletions packages/apple-llm/ios/AppleLLMImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,39 @@ public class AppleLLMImpl: NSObject {

private var streamTasks: [String: Task<Void, Never>] = [:]

@objc
public func isAvailable() -> Bool {
private func currentAvailability() -> AppleAvailability {
#if canImport(FoundationModels)
if #available(iOS 26, *) {
return SystemLanguageModel.default.availability == .available
switch SystemLanguageModel.default.availability {
case .available:
return .available
case .unavailable(.deviceNotEligible):
return .deviceNotEligible
case .unavailable(.appleIntelligenceNotEnabled):
return .appleIntelligenceNotEnabled
case .unavailable(.modelNotReady):
return .modelNotReady
case .unavailable:
return .unknown
}
} else {
return false
return .unsupportedOS
}
#else
return false
return .unsupportedOS
#endif
}

@objc
public func getAvailability() -> String {
return currentAvailability().rawValue
}

@objc
public func isAvailable() -> Bool {
return currentAvailability() == .available
}

@objc
public func countTokens(
_ text: String,
Expand All @@ -41,10 +61,11 @@ public class AppleLLMImpl: NSObject {
) {
#if canImport(FoundationModels)
if #available(iOS 26.4, *) {
guard SystemLanguageModel.default.availability == .available else {
let availability = currentAvailability()
guard availability == .available else {
reject(
"MODEL_UNAVAILABLE",
"Apple Intelligence model is not available",
availability.unavailableDescription,
nil
)
return
Expand Down Expand Up @@ -77,8 +98,9 @@ public class AppleLLMImpl: NSObject {
) {
#if canImport(FoundationModels)
if #available(iOS 26, *) {
guard SystemLanguageModel.default.availability == .available else {
rejectWithAppleError(.modelUnavailable, reject: reject)
let availability = currentAvailability()
guard availability == .available else {
rejectWithAppleError(.modelUnavailable(availability), reject: reject)
return
}

Expand Down Expand Up @@ -144,8 +166,9 @@ public class AppleLLMImpl: NSObject {
) {
#if canImport(FoundationModels)
if #available(iOS 26, *) {
guard SystemLanguageModel.default.availability == .available else {
emitStreamError(.modelUnavailable, streamId: streamId, onError: onError)
let availability = currentAvailability()
guard availability == .available else {
emitStreamError(.modelUnavailable(availability), streamId: streamId, onError: onError)
return
}

Expand Down
1 change: 1 addition & 0 deletions packages/apple-llm/src/AppleFoundationModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const nativeAppleLLM = NativeAppleLLM as Spec & {

const AppleFoundationModels: Spec = {
isAvailable: () => NativeAppleLLM.isAvailable(),
getAvailability: () => NativeAppleLLM.getAvailability(),
countTokens: (text) => {
if (typeof nativeAppleLLM.countTokens !== 'function') {
return Promise.reject(new Error(tokenCountingUnavailableMessage))
Expand Down
22 changes: 22 additions & 0 deletions packages/apple-llm/src/NativeAppleLLM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,27 @@ export interface AppleMessage {
content: string
}

/**
* Availability of Apple Intelligence, mirroring `SystemLanguageModel.Availability`.
*
* - `available` - the model is ready to use
* - `deviceNotEligible` - the hardware does not support Apple Intelligence
* - `appleIntelligenceNotEnabled` - the user has not turned Apple Intelligence
* on, so they can be pointed at Settings
* - `modelNotReady` - Apple Intelligence is on but the model is not downloaded
* yet, so it is worth retrying later
* - `unsupportedOS` - the device runs an OS older than iOS 26
* - `unknown` - Apple reported a reason this version of the library does not
* know about yet
*/
export type AppleAvailability =
| 'available'
| 'deviceNotEligible'
| 'appleIntelligenceNotEnabled'
| 'modelNotReady'
| 'unsupportedOS'
| 'unknown'

export interface AppleGenerationOptions {
temperature?: number
maxTokens?: number
Expand All @@ -36,6 +57,7 @@ export type StreamErrorEvent = {

export interface Spec extends TurboModule {
isAvailable(): boolean
getAvailability(): AppleAvailability
countTokens(text: string): Promise<number>
generateText(
messages: AppleMessage[],
Expand Down
1 change: 1 addition & 0 deletions packages/apple-llm/src/ai-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function createAppleProvider({
return createLanguageModel()
}
provider.isAvailable = () => NativeAppleLLM.isAvailable()
provider.getAvailability = () => NativeAppleLLM.getAvailability()
provider.languageModel = createLanguageModel
provider.textEmbeddingModel = (options: AppleEmbeddingOptions = {}) => {
return new AppleTextEmbeddingModel(options)
Expand Down
1 change: 1 addition & 0 deletions packages/apple-llm/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { default as AppleFoundationModels } from './AppleFoundationModels'
export type { AppleLLMError, AppleLLMErrorCode } from './errors'
export { AppleLLMErrorCodes } from './errors'
export { default as AppleEmbeddings } from './NativeAppleEmbeddings'
export type { AppleAvailability } from './NativeAppleLLM'
export { default as AppleSpeech, VoiceInfo } from './NativeAppleSpeech'
export { default as AppleTranscription } from './NativeAppleTranscription'
export { default as AppleUtils } from './NativeAppleUtils'
Expand Down
6 changes: 6 additions & 0 deletions skills/react-native-ai/references/apple-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ if (apple.isAvailable()) {
}
```

Use `apple.getAvailability()` when you need to know *why* it is unavailable:
`'available' | 'deviceNotEligible' | 'appleIntelligenceNotEnabled' |
'modelNotReady' | 'unsupportedOS' | 'unknown'`. In particular
`'appleIntelligenceNotEnabled'` is user-fixable, so prompt them to turn Apple
Intelligence on in Settings instead of showing a generic error.

### 3. Model Types

| Type | Method | Use Case | Documentation |
Expand Down
44 changes: 44 additions & 0 deletions website/src/docs/apple/generating.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,47 @@ if (!apple.isAvailable()) {
}
```

### Availability Status

`isAvailable()` only tells you whether you can generate right now. When it
returns `false`, use `getAvailability()` to find out why, so you can show the
user something more useful than a generic error:

```typescript
import { apple, type AppleAvailability } from '@react-native-ai/apple';

function describeAvailability(status: AppleAvailability) {
switch (status) {
case 'available':
return null;
case 'appleIntelligenceNotEnabled':
// Actionable: the user can fix this in Settings
return 'Turn on Apple Intelligence in Settings to use this feature.';
case 'modelNotReady':
// Temporary: the model is still downloading, worth retrying later
return 'Apple Intelligence is still getting ready. Please try again later.';
case 'deviceNotEligible':
case 'unsupportedOS':
case 'unknown':
// Not recoverable on this device: fall back to another provider
return null;
}
}

const message = describeAvailability(apple.getAvailability());
```

| Status | Meaning |
| ----------------------------- | ------------------------------------------------------------- |
| `available` | The model is ready to use |
| `deviceNotEligible` | The hardware does not support Apple Intelligence |
| `appleIntelligenceNotEnabled` | The user has not turned Apple Intelligence on in Settings |
| `modelNotReady` | Apple Intelligence is on, but the model is not downloaded yet |
| `unsupportedOS` | The device runs an OS older than iOS 26 |
| `unknown` | Apple reported a reason this library does not know about yet |

`isAvailable()` is equivalent to `getAvailability() === 'available'`.

## Context Window

Apple Foundation Models have a fixed context window of 4096 tokens. This limit applies to the full request context, including system instructions, previous conversation messages, tool definitions, schemas, and the current user prompt.
Expand Down Expand Up @@ -454,6 +495,9 @@ import { AppleFoundationModels } from '@react-native-ai/apple'
// Check if Apple Intelligence is available
const isAvailable = AppleFoundationModels.isAvailable()

// Or check why it is unavailable
const availability = AppleFoundationModels.getAvailability()

// Generate text responses
const messages = [{ role: 'user', content: 'Hello' }]
const options = { temperature: 0.7, maxTokens: 100 }
Expand Down
1 change: 1 addition & 0 deletions website/src/docs/apple/running-on-simulator.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ If Apple Intelligence is enabled but models aren't working:
- Ensure the model download completed successfully on macOS
- Restart the iOS Simulator
- Verify that `apple.isAvailable()` returns `true` in your code (see [Availability Check](./generating#availability-check))
- If it returns `false`, call `apple.getAvailability()` to see the exact reason (see [Availability Status](./generating#availability-status))
- Check that your app is running on iOS 26+ in the simulator

## API Availability
Expand Down