foundation-models 0.11.3

Safe Rust bindings for Apple's FoundationModels framework - on-device LLM on macOS 26+
Documentation
import Foundation

#if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
import FoundationModels
#endif

#if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
@available(macOS 26.0, *)
func writeErrorOut(
    _ errorOut: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?,
    _ payload: BridgeErrorPayload
) {
    errorOut?.pointee = ffiString(encodeErrorPayload(payload))
}

@available(macOS 26.0, *)
func generationErrorPayload(_ error: LanguageModelSession.GenerationError) -> BridgeErrorPayload {
    let message = error.localizedDescription
    let recoverySuggestion = error.recoverySuggestion
    let failureReason = error.failureReason

    switch error {
    case .guardrailViolation(let context),
         .exceededContextWindowSize(let context),
         .unsupportedLanguageOrLocale(let context),
         .assetsUnavailable(let context),
         .rateLimited(let context),
         .decodingFailure(let context),
         .concurrentRequests(let context),
         .unsupportedGuide(let context):
        return BridgeErrorPayload(
            message: message,
            recoverySuggestion: recoverySuggestion,
            failureReason: failureReason,
            generationErrorContext: BridgeErrorContext(debugDescription: context.debugDescription),
            refusal: nil,
            toolCallError: nil,
            adapterAssetErrorContext: nil,
            schemaErrorContext: nil
        )
    case .refusal(let refusal, let context):
        return BridgeErrorPayload(
            message: message,
            recoverySuggestion: recoverySuggestion,
            failureReason: failureReason,
            generationErrorContext: BridgeErrorContext(debugDescription: context.debugDescription),
            refusal: bridgeRefusal(refusal),
            toolCallError: nil,
            adapterAssetErrorContext: nil,
            schemaErrorContext: nil
        )
    @unknown default:
        return BridgeErrorPayload(
            message: message,
            recoverySuggestion: recoverySuggestion,
            failureReason: failureReason,
            generationErrorContext: nil,
            refusal: nil,
            toolCallError: nil,
            adapterAssetErrorContext: nil,
            schemaErrorContext: nil
        )
    }
}

@available(macOS 26.0, *)
func schemaErrorPayload(_ error: GenerationSchema.SchemaError) -> BridgeErrorPayload {
    let context: GenerationSchema.SchemaError.Context
    switch error {
    case .duplicateType(_, _, let errorContext),
         .duplicateProperty(_, _, let errorContext),
         .emptyTypeChoices(_, let errorContext),
         .undefinedReferences(_, _, let errorContext):
        context = errorContext
    @unknown default:
        return BridgeErrorPayload(
            message: error.localizedDescription,
            recoverySuggestion: error.recoverySuggestion,
            failureReason: nil,
            generationErrorContext: nil,
            refusal: nil,
            toolCallError: nil,
            adapterAssetErrorContext: nil,
            schemaErrorContext: nil
        )
    }

    return BridgeErrorPayload(
        message: error.localizedDescription,
        recoverySuggestion: error.recoverySuggestion,
        failureReason: nil,
        generationErrorContext: nil,
        refusal: nil,
        toolCallError: nil,
        adapterAssetErrorContext: nil,
        schemaErrorContext: BridgeErrorContext(debugDescription: context.debugDescription)
    )
}

@available(macOS 26.0, *)
func toolCallErrorPayload(_ error: LanguageModelSession.ToolCallError) -> BridgeErrorPayload {
    BridgeErrorPayload(
        message: error.localizedDescription,
        recoverySuggestion: nil,
        failureReason: nil,
        generationErrorContext: nil,
        refusal: nil,
        toolCallError: BridgeToolCallErrorPayload(
            tool: bridgeToolDefinition(from: error.tool),
            underlyingError: error.underlyingError.localizedDescription
        ),
        adapterAssetErrorContext: nil,
        schemaErrorContext: nil
    )
}

@available(macOS 26.0, *)
func assetErrorPayload(_ error: SystemLanguageModel.Adapter.AssetError) -> BridgeErrorPayload {
    let message = error.localizedDescription
    let recoverySuggestion = error.recoverySuggestion
    
    var context: BridgeErrorContext? = nil
    switch error {
    case .invalidAsset(let errorContext),
         .invalidAdapterName(let errorContext),
         .compatibleAdapterNotFound(let errorContext):
        context = BridgeErrorContext(debugDescription: errorContext.debugDescription)
    @unknown default:
        break
    }
    
    return BridgeErrorPayload(
        message: message,
        recoverySuggestion: recoverySuggestion,
        failureReason: nil,
        generationErrorContext: nil,
        refusal: nil,
        toolCallError: nil,
        adapterAssetErrorContext: context,
        schemaErrorContext: nil
    )
}

@available(macOS 26.0, *)
func encodedTextResponse(_ response: LanguageModelSession.Response<String>) throws -> String {
    let transcriptJSON = try encodeTranscriptJSON(entries: response.transcriptEntries)
    let payload = BridgeTextResponse(
        content: response.content,
        rawContent: bridgeGeneratedContent(response.rawContent),
        transcriptJSON: transcriptJSON
    )
    return try encodeBridge(payload)
}

@available(macOS 26.0, *)
func streamTextResponse(
    _ stream: LanguageModelSession.ResponseStream<String>,
    context: UnsafeMutableRawPointer?,
    callback: @convention(c) (
        UnsafeMutableRawPointer?,
        UnsafeMutablePointer<CChar>?,
        Bool,
        Int32
    ) -> Void
) async {
    do {
        var lastEmitted = ""
        for try await snapshot in stream {
            let full = snapshot.content
            let delta: String
            if full.hasPrefix(lastEmitted) {
                delta = String(full.dropFirst(lastEmitted.count))
            } else {
                delta = full
            }
            lastEmitted = full
            let payload = BridgeTextStreamSnapshot(
                delta: delta,
                content: full,
                rawContent: bridgeGeneratedContent(snapshot.rawContent)
            )
            callback(context, ffiString(try encodeBridge(payload)), false, FM_OK)
        }
        callback(context, nil, true, FM_OK)
    } catch {
        let (code, message) = mapError(error)
        callback(context, ffiString(message), true, code)
    }
}
#endif

@_cdecl("fm_generation_id_create")
public func fm_generation_id_create(
    _ outputOut: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?,
    _ errorOut: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        let handle = GenerationIDRegistry.shared.register(GenerationID())
        outputOut?.pointee = ffiString((try? encodeBridge(handle)) ?? "")
        return FM_OK
    }
    #endif
    writeErrorOut(errorOut, "FoundationModels requires macOS 26.0 or newer")
    return FM_MODEL_UNAVAILABLE
}

@_cdecl("fm_decimal_to_generated_content_json")
public func fm_decimal_to_generated_content_json(
    _ decimalString: UnsafePointer<CChar>,
    _ outputOut: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?,
    _ errorOut: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        guard let decimal = Decimal(string: String(cString: decimalString)) else {
            writeErrorOut(errorOut, BridgeErrorPayload(
                message: "decimal value is invalid",
                recoverySuggestion: nil,
                failureReason: nil,
                generationErrorContext: nil,
                refusal: nil,
                toolCallError: nil,
                adapterAssetErrorContext: nil,
                schemaErrorContext: nil
            ))
            return FM_INVALID_ARGUMENT
        }
        outputOut?.pointee = ffiString(decimal.generatedContent.jsonString)
        return FM_OK
    }
    #endif
    writeErrorOut(errorOut, "FoundationModels requires macOS 26.0 or newer")
    return FM_MODEL_UNAVAILABLE
}

@_cdecl("fm_decimal_from_generated_content_json")
public func fm_decimal_from_generated_content_json(
    _ generatedContentJSON: UnsafePointer<CChar>,
    _ outputOut: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?,
    _ errorOut: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>?
) -> Int32 {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        do {
            let content = try GeneratedContent(json: String(cString: generatedContentJSON))
            let decimal = try Decimal(content)
            outputOut?.pointee = ffiString(NSDecimalNumber(decimal: decimal).stringValue)
            return FM_OK
        } catch {
            let (code, message) = mapError(error)
            errorOut?.pointee = ffiString(message)
            return code
        }
    }
    #endif
    writeErrorOut(errorOut, "FoundationModels requires macOS 26.0 or newer")
    return FM_MODEL_UNAVAILABLE
}

@_cdecl("fm_refusal_explanation_json")
public func fm_refusal_explanation_json(
    _ refusalToken: UnsafePointer<CChar>,
    _ context: UnsafeMutableRawPointer?,
    _ callback: @convention(c) (
        UnsafeMutableRawPointer?,
        UnsafeMutablePointer<CChar>?,
        UnsafeMutablePointer<CChar>?,
        Int32
    ) -> Void
) {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        let bridgeRefusal = BridgeRefusal(token: String(cString: refusalToken))
        guard let refusal = RefusalRegistry.shared.resolve(bridgeRefusal) else {
            callback(context, nil, ffiString("unknown refusal token"), FM_INVALID_ARGUMENT)
            return
        }
        Task.detached {
            do {
                let response = try await refusal.explanation
                callback(context, ffiString(try encodedTextResponse(response)), nil, FM_OK)
            } catch {
                let (code, message) = mapError(error)
                callback(context, nil, ffiString(message), code)
            }
        }
        return
    }
    #endif
    callback(context, nil, ffiString("FoundationModels requires macOS 26.0 or newer"), FM_MODEL_UNAVAILABLE)
}

@_cdecl("fm_refusal_explanation_from_transcript_json")
public func fm_refusal_explanation_from_transcript_json(
    _ transcriptJSON: UnsafePointer<CChar>,
    _ context: UnsafeMutableRawPointer?,
    _ callback: @convention(c) (
        UnsafeMutableRawPointer?,
        UnsafeMutablePointer<CChar>?,
        UnsafeMutablePointer<CChar>?,
        Int32
    ) -> Void
) {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        Task.detached {
            do {
                let transcript = try decodeTranscript(from: String(cString: transcriptJSON))
                let refusal = LanguageModelSession.GenerationError.Refusal(
                    transcriptEntries: Array(transcript)
                )
                let response = try await refusal.explanation
                callback(context, ffiString(try encodedTextResponse(response)), nil, FM_OK)
            } catch {
                let (code, message) = mapError(error)
                callback(context, nil, ffiString(message), code)
            }
        }
        return
    }
    #endif
    callback(context, nil, ffiString("FoundationModels requires macOS 26.0 or newer"), FM_MODEL_UNAVAILABLE)
}

@_cdecl("fm_refusal_explanation_stream")
public func fm_refusal_explanation_stream(
    _ refusalToken: UnsafePointer<CChar>,
    _ context: UnsafeMutableRawPointer?,
    _ callback: @convention(c) (
        UnsafeMutableRawPointer?,
        UnsafeMutablePointer<CChar>?,
        Bool,
        Int32
    ) -> Void
) {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        let bridgeRefusal = BridgeRefusal(token: String(cString: refusalToken))
        guard let refusal = RefusalRegistry.shared.resolve(bridgeRefusal) else {
            callback(context, ffiString("unknown refusal token"), true, FM_INVALID_ARGUMENT)
            return
        }
        Task.detached {
            await streamTextResponse(refusal.explanationStream, context: context, callback: callback)
        }
        return
    }
    #endif
    callback(context, ffiString("FoundationModels requires macOS 26.0 or newer"), true, FM_MODEL_UNAVAILABLE)
}

@_cdecl("fm_refusal_explanation_stream_from_transcript_json")
public func fm_refusal_explanation_stream_from_transcript_json(
    _ transcriptJSON: UnsafePointer<CChar>,
    _ context: UnsafeMutableRawPointer?,
    _ callback: @convention(c) (
        UnsafeMutableRawPointer?,
        UnsafeMutablePointer<CChar>?,
        Bool,
        Int32
    ) -> Void
) {
    #if canImport(FoundationModels) && FOUNDATION_MODELS_HAS_MACOS26_SDK
    if #available(macOS 26.0, *) {
        Task.detached {
            do {
                let transcript = try decodeTranscript(from: String(cString: transcriptJSON))
                let refusal = LanguageModelSession.GenerationError.Refusal(
                    transcriptEntries: Array(transcript)
                )
                await streamTextResponse(refusal.explanationStream, context: context, callback: callback)
            } catch {
                let (code, message) = mapError(error)
                callback(context, ffiString(message), true, code)
            }
        }
        return
    }
    #endif
    callback(context, ffiString("FoundationModels requires macOS 26.0 or newer"), true, FM_MODEL_UNAVAILABLE)
}