From 75c9beb8cbbc8c42676cf64228c2a107b7e13f62 Mon Sep 17 00:00:00 2001 From: Simba Zhang Date: Wed, 28 Oct 2026 22:29:05 -0700 Subject: [PATCH] fix: make the Generation handlers tolerate events they do not model mlx-swift-lm's next upstream sync adds a `.reasoning` case to `Generation`, which makes the four `switch generation` statements in Server.swift non-exhaustive and breaks the build. Give each a `default: break` (no reference to `.reasoning`, so it compiles against the current and the next mlx-swift-lm). Also hand the standard generation path a context without a reasoning protocol, so the loop keeps emitting thinking inline as `.chunk` text, which ThinkingStateTracker already splits into reasoning_content. The thinking budget still reads the container's config. Co-Authored-By: Claude Sonnet 5.5 --- Sources/SwiftLM/Server.swift | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Sources/SwiftLM/Server.swift b/Sources/SwiftLM/Server.swift index 26b01df..1bba8f5 100644 --- a/Sources/SwiftLM/Server.swift +++ b/Sources/SwiftLM/Server.swift @@ -2716,6 +2716,13 @@ func handleChatCompletion( // ── Cache-aware generation (standard path) ── let (stream, onPrefillDone) = try await container.perform { context -> (AsyncStream, (() async -> Void)?) in + // mlx-swift-lm can route a model's thinking into separate `.reasoning` events, with the + // delimiters removed. This server splits `…` out of `.chunk` text itself + // (`ThinkingStateTracker`) and maps it to `reasoning_content`, so the generation loop is + // given a context without a reasoning protocol and keeps emitting the text inline. The + // thinking budget above still reads the real config from the container. + var context = context + context.configuration.reasoningConfig = nil let cache = try context.model.newCache(parameters: params) // ── TurboQuant: enable 3-bit KV compression on every KVCacheSimple layer ── @@ -3504,6 +3511,10 @@ func handleChatStreaming( fflush(stdout) } } + default: + // Events this build does not model (mlx-swift-lm's `.reasoning`, from the 2026-10 + // upstream sync) are ignored: SwiftLM still splits `` out of `.chunk` text itself. + break } } // Safety net: the tail is normally released in `case .info`, but a stream that @@ -3608,6 +3619,10 @@ func handleChatNonStreaming( if let onPrefillDone { await onPrefillDone() } generationStopReason = info.stopReason completionTokenCount = info.generationTokenCount // real count; see handleChatStreaming + default: + // Events this build does not model (mlx-swift-lm's `.reasoning`, from the 2026-10 + // upstream sync) are ignored: SwiftLM still splits `` out of `.chunk` text itself. + break } } print("") // end the real-time token stream line @@ -3954,6 +3969,10 @@ func handleTextStreaming( cont.yield("data: [DONE]\n\n") cont.finish() } + default: + // Events this build does not model (mlx-swift-lm's `.reasoning`, from the 2026-10 + // upstream sync) are ignored: SwiftLM still splits `` out of `.chunk` text itself. + break } } // The tail never became a stop sequence, so it is ordinary output. Normally @@ -4009,6 +4028,10 @@ func handleTextNonStreaming( completionTokenCount = info.generationTokenCount // real count; see handleChatStreaming case .toolCall, .rejectedToolCall: break + default: + // Events this build does not model (mlx-swift-lm's `.reasoning`, from the 2026-10 + // upstream sync) are ignored: SwiftLM still splits `` out of `.chunk` text itself. + break } } let duration = Date().timeIntervalSince(genStart)