From e6c2bed0fe744c1632aac7c0ea67e4fde7b653ac Mon Sep 17 00:00:00 2001 From: Peera Kerdkokaew Date: Wed, 24 Jun 2026 18:56:48 +0700 Subject: [PATCH 1/2] Fix Xcode 27 "switch must be exhaustive" build error The Xcode 27 SDK adds new cases to non-frozen Foundation Models enums: - Transcript.Entry gains `.reasoning` - Transcript.Segment gains `.attachment` and `.custom` These broke the exhaustive switch statements in mapTranscriptEntries and serializeSegment, causing a hard "switch must be exhaustive" compile error. Add `@unknown default` clauses to all switches over non-frozen Apple framework enums (Transcript.Entry, Transcript.Segment, and LanguageModelSession.GenerationError) so the plugin keeps compiling as Apple adds enum cases in future SDKs. Applied to both the iOS and macOS plugin sources. Bump version to 0.2.2 and update CHANGELOG. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 6 ++++++ ios/Classes/FoundationModelsFrameworkPlugin.swift | 14 ++++++++++++++ .../Classes/FoundationModelsFrameworkPlugin.swift | 14 ++++++++++++++ pubspec.yaml | 2 +- 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 902971f..2755ae7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.2.2 + +### 🐛 Bug Fixes +- **Fixed Xcode 27 build failure** ("switch must be exhaustive") in the iOS and macOS plugins. The Xcode 27 SDK adds new cases to the non-frozen Foundation Models enums `Transcript.Entry` (`.reasoning`) and `Transcript.Segment` (`.attachment`, `.custom`), which broke the exhaustive `switch` statements in `serializeSegment` and `mapTranscriptEntries`. +- Added `@unknown default` clauses to all `switch` statements over non-frozen Apple framework enums (`Transcript.Entry`, `Transcript.Segment`, and `LanguageModelSession.GenerationError`) so the plugin keeps compiling as Apple adds enum cases in future SDKs. + ## 0.2.1 ### 📚 Documentation Improvements diff --git a/ios/Classes/FoundationModelsFrameworkPlugin.swift b/ios/Classes/FoundationModelsFrameworkPlugin.swift index d0f4db8..f0c21c3 100644 --- a/ios/Classes/FoundationModelsFrameworkPlugin.swift +++ b/ios/Classes/FoundationModelsFrameworkPlugin.swift @@ -365,6 +365,13 @@ import AppKit role = "response" segmentsText = response.segments.map(serializeSegment) content = segmentsText.joined(separator: "\n") + @unknown default: + // Handle transcript entry kinds added in newer SDKs (e.g. `.reasoning` + // introduced in the Xcode 27 / iOS 26+ SDK) without breaking the build. + role = "unknown" + let description = String(describing: entry) + segmentsText = [description] + content = description } return TranscriptEntry(id: entry.id, role: role, content: content, segments: segmentsText) @@ -378,6 +385,10 @@ import AppKit return textSegment.content case .structure(let structuredSegment): return String(describing: structuredSegment.content) + @unknown default: + // Handle transcript segment kinds added in newer SDKs (e.g. `.attachment` + // and `.custom` introduced in the Xcode 27 / iOS 26+ SDK). + return String(describing: segment) } } @available(iOS 26.0, macOS 15.0, *) @@ -430,6 +441,9 @@ import AppKit return FoundationModelsError.requestFailed("Unsupported guide") case .unsupportedLanguageOrLocale(_): return FoundationModelsError.requestFailed("Unsupported language or locale") + @unknown default: + // Handle generation error cases added in newer SDKs without breaking the build. + return FoundationModelsError.requestFailed("Generation failed") } } diff --git a/macos/Classes/FoundationModelsFrameworkPlugin.swift b/macos/Classes/FoundationModelsFrameworkPlugin.swift index 4d369d6..cc91f14 100644 --- a/macos/Classes/FoundationModelsFrameworkPlugin.swift +++ b/macos/Classes/FoundationModelsFrameworkPlugin.swift @@ -365,6 +365,13 @@ import AppKit role = "response" segmentsText = response.segments.map(serializeSegment) content = segmentsText.joined(separator: "\n") + @unknown default: + // Handle transcript entry kinds added in newer SDKs (e.g. `.reasoning` + // introduced in the Xcode 27 / macOS 26+ SDK) without breaking the build. + role = "unknown" + let description = String(describing: entry) + segmentsText = [description] + content = description } return TranscriptEntry(id: entry.id, role: role, content: content, segments: segmentsText) @@ -378,6 +385,10 @@ import AppKit return textSegment.content case .structure(let structuredSegment): return String(describing: structuredSegment.content) + @unknown default: + // Handle transcript segment kinds added in newer SDKs (e.g. `.attachment` + // and `.custom` introduced in the Xcode 27 / macOS 26+ SDK). + return String(describing: segment) } } @available(iOS 26.0, macOS 26.0, *) @@ -430,6 +441,9 @@ import AppKit return FoundationModelsError.requestFailed("Unsupported guide") case .unsupportedLanguageOrLocale(_): return FoundationModelsError.requestFailed("Unsupported language or locale") + @unknown default: + // Handle generation error cases added in newer SDKs without breaking the build. + return FoundationModelsError.requestFailed("Generation failed") } } diff --git a/pubspec.yaml b/pubspec.yaml index ecc916d..00ac37d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: foundation_models_framework description: A Flutter package for integrating with Apple's Foundation Models framework on iOS and macOS devices. Provides session-based language model interactions with Apple Intelligence features. -version: 0.2.1 +version: 0.2.2 homepage: https://github.com/dmakwt/flutter_foundation_models_framework repository: https://github.com/dmakwt/flutter_foundation_models_framework issue_tracker: https://github.com/dmakwt/flutter_foundation_models_framework/issues From e58d20f07e4d99e0c4323708e79734bd048e54ff Mon Sep 17 00:00:00 2001 From: Peera Kerdkokaew Date: Wed, 24 Jun 2026 19:35:57 +0700 Subject: [PATCH 2/2] Add first-class reasoning/attachment/custom handling (Xcode 27+) Layer explicit handling of the new non-frozen enum cases on top of the `@unknown default` compile fix, gated behind `#if compiler(>=6.4)`: - Transcript.Entry `.reasoning` -> serialized with a dedicated "reasoning" role and its segments mapped like other entries. - Transcript.Segment `.attachment` / `.custom` -> serialized explicitly. The `#if compiler(>=6.4)` gate (Swift 6.4 ships with Xcode 27) keeps the plugin building on Xcode 26, where these cases do not exist in the SDK; older toolchains fall back to the `@unknown default` branch. `@unknown default` is retained on all switches since the enums remain non-frozen. Verified against the iOS 27 SDK: gate-on path typechecks with zero diagnostics; gate-off path has zero errors. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 3 +++ .../FoundationModelsFrameworkPlugin.swift | 24 +++++++++++++++---- .../FoundationModelsFrameworkPlugin.swift | 24 +++++++++++++++---- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2755ae7..47672c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ - **Fixed Xcode 27 build failure** ("switch must be exhaustive") in the iOS and macOS plugins. The Xcode 27 SDK adds new cases to the non-frozen Foundation Models enums `Transcript.Entry` (`.reasoning`) and `Transcript.Segment` (`.attachment`, `.custom`), which broke the exhaustive `switch` statements in `serializeSegment` and `mapTranscriptEntries`. - Added `@unknown default` clauses to all `switch` statements over non-frozen Apple framework enums (`Transcript.Entry`, `Transcript.Segment`, and `LanguageModelSession.GenerationError`) so the plugin keeps compiling as Apple adds enum cases in future SDKs. +### ✨ Improvements +- **First-class handling of reasoning transcripts on Xcode 27+.** When built with a Swift 6.4+ toolchain (Xcode 27+), `.reasoning` transcript entries are serialized with a dedicated `reasoning` role and their segments are mapped just like other entries, and `.attachment`/`.custom` segments are serialized explicitly. This is gated behind `#if compiler(>=6.4)` so the plugin continues to build on Xcode 26 (where those cases do not exist), falling back to the `@unknown default` behavior there. + ## 0.2.1 ### 📚 Documentation Improvements diff --git a/ios/Classes/FoundationModelsFrameworkPlugin.swift b/ios/Classes/FoundationModelsFrameworkPlugin.swift index f0c21c3..30893fe 100644 --- a/ios/Classes/FoundationModelsFrameworkPlugin.swift +++ b/ios/Classes/FoundationModelsFrameworkPlugin.swift @@ -365,9 +365,17 @@ import AppKit role = "response" segmentsText = response.segments.map(serializeSegment) content = segmentsText.joined(separator: "\n") + #if compiler(>=6.4) + // `.reasoning` was added to `Transcript.Entry` in the Xcode 27 / iOS 26+ SDK. + // Gate it on the Swift 6.4+ toolchain (Xcode 27+) so this still builds on + // Xcode 26, where the case does not exist; older toolchains use `@unknown default`. + case .reasoning(let reasoning): + role = "reasoning" + segmentsText = reasoning.segments.map(serializeSegment) + content = segmentsText.joined(separator: "\n") + #endif @unknown default: - // Handle transcript entry kinds added in newer SDKs (e.g. `.reasoning` - // introduced in the Xcode 27 / iOS 26+ SDK) without breaking the build. + // Handle any further entry kinds added in future SDKs without breaking the build. role = "unknown" let description = String(describing: entry) segmentsText = [description] @@ -385,9 +393,17 @@ import AppKit return textSegment.content case .structure(let structuredSegment): return String(describing: structuredSegment.content) + #if compiler(>=6.4) + // `.attachment` and `.custom` were added to `Transcript.Segment` in the + // Xcode 27 / iOS 26+ SDK. Gate them on the Swift 6.4+ toolchain (Xcode 27+) so + // this still builds on Xcode 26; older toolchains use `@unknown default`. + case .attachment(let attachmentSegment): + return String(describing: attachmentSegment) + case .custom(let customSegment): + return String(describing: customSegment) + #endif @unknown default: - // Handle transcript segment kinds added in newer SDKs (e.g. `.attachment` - // and `.custom` introduced in the Xcode 27 / iOS 26+ SDK). + // Handle any further segment kinds added in future SDKs. return String(describing: segment) } } diff --git a/macos/Classes/FoundationModelsFrameworkPlugin.swift b/macos/Classes/FoundationModelsFrameworkPlugin.swift index cc91f14..790cd4d 100644 --- a/macos/Classes/FoundationModelsFrameworkPlugin.swift +++ b/macos/Classes/FoundationModelsFrameworkPlugin.swift @@ -365,9 +365,17 @@ import AppKit role = "response" segmentsText = response.segments.map(serializeSegment) content = segmentsText.joined(separator: "\n") + #if compiler(>=6.4) + // `.reasoning` was added to `Transcript.Entry` in the Xcode 27 / macOS 26+ SDK. + // Gate it on the Swift 6.4+ toolchain (Xcode 27+) so this still builds on + // Xcode 26, where the case does not exist; older toolchains use `@unknown default`. + case .reasoning(let reasoning): + role = "reasoning" + segmentsText = reasoning.segments.map(serializeSegment) + content = segmentsText.joined(separator: "\n") + #endif @unknown default: - // Handle transcript entry kinds added in newer SDKs (e.g. `.reasoning` - // introduced in the Xcode 27 / macOS 26+ SDK) without breaking the build. + // Handle any further entry kinds added in future SDKs without breaking the build. role = "unknown" let description = String(describing: entry) segmentsText = [description] @@ -385,9 +393,17 @@ import AppKit return textSegment.content case .structure(let structuredSegment): return String(describing: structuredSegment.content) + #if compiler(>=6.4) + // `.attachment` and `.custom` were added to `Transcript.Segment` in the + // Xcode 27 / macOS 26+ SDK. Gate them on the Swift 6.4+ toolchain (Xcode 27+) so + // this still builds on Xcode 26; older toolchains use `@unknown default`. + case .attachment(let attachmentSegment): + return String(describing: attachmentSegment) + case .custom(let customSegment): + return String(describing: customSegment) + #endif @unknown default: - // Handle transcript segment kinds added in newer SDKs (e.g. `.attachment` - // and `.custom` introduced in the Xcode 27 / macOS 26+ SDK). + // Handle any further segment kinds added in future SDKs. return String(describing: segment) } }