From 4b65d8147744e2aa1ff935613f8a0b51f37ff7f4 Mon Sep 17 00:00:00 2001 From: RaajeevChandran Date: Tue, 6 Oct 2026 10:18:29 +0000 Subject: [PATCH 1/4] send the native mtp option to the phone's model picker for bundles with an mtp head --- .../Configuration/ModelOptionsSnapshot.swift | 72 ++++++++++++++++++- .../OsaurusCore/Networking/HTTPHandler.swift | 21 +++++- docs/MOBILE_PROTOCOL.md | 5 ++ 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/Packages/OsaurusCore/Models/Configuration/ModelOptionsSnapshot.swift b/Packages/OsaurusCore/Models/Configuration/ModelOptionsSnapshot.swift index ef83f4264..02ac60ca4 100644 --- a/Packages/OsaurusCore/Models/Configuration/ModelOptionsSnapshot.swift +++ b/Packages/OsaurusCore/Models/Configuration/ModelOptionsSnapshot.swift @@ -56,11 +56,30 @@ struct ModelOptionsSnapshot: Encodable, Equatable { case invalidValue } + /// Option id of the Native MTP row, the composer's + /// `FloatingInputCard.nativeMTPOptionID`. + static let nativeMTPOptionId = "nativeMTPDepth" + + /// What the model's bundle offers for Native MTP, as composer selection + /// reads it: nil without an MTP head, `blocked` when its tuning artifact + /// refuses MTP. + struct NativeMTP: Equatable, Sendable { + let blocked: Bool + + /// File-only bundle inspection; run it off the main thread. + static func inspect(model: String) -> NativeMTP? { + guard let status = ModelRuntime.inspectLoadingModelMTP(name: model), + status.bundleHasMTP, status.isTargetMTPFamily + else { return nil } + return NativeMTP(blocked: status.isBlocked) + } + } + /// Built after `LocalReasoningCapability.resolveForDispatch` so local /// bundles report their real thinking / effort contract, not the cold /// main-thread miss. @MainActor - static func make(for model: String) -> ModelOptionsSnapshot { + static func make(for model: String, nativeMTP: NativeMTP? = nil) -> ModelOptionsSnapshot { let values = ModelProfileRegistry.normalizedOptions( for: model, persisted: ModelOptionsStore.shared.loadOptions(for: model) @@ -135,7 +154,56 @@ struct ModelOptionsSnapshot: Encodable, Equatable { ) } } - return ModelOptionsSnapshot(model: model, thinking: thinking, options: options) + return ModelOptionsSnapshot( + model: model, + thinking: thinking, + options: options + [nativeMTP.map(nativeMTPOption)].compactMap { $0 } + ) + } + + /// The composer's Native MTP row. MTP lives in the global server + /// settings, not the per-model store, so the selection is read there; + /// a blocked bundle only offers Off. + @MainActor + private static func nativeMTPOption(_ nativeMTP: NativeMTP) -> Option { + let off = Segment(id: "off", label: L("Off (AR)"), description: nil) + let adaptive = Segment(id: "auto", label: L("On (Adaptive)"), description: nil) + let mode = ServerController.runtimeSettingsForConfigureTool().settings.mtp.mode + let selected = nativeMTP.blocked || mode == .off ? "off" : "auto" + return Option( + id: nativeMTPOptionId, + label: L("Native MTP"), + icon: "hare", + help: nativeMTP.blocked + ? L( + "Speculative decoding is disabled for this bundle because its MTP head is not safe for production use." + ) + : L( + "Off uses ordinary autoregressive decoding. On requests adaptive native MTP when the bundle passes runtime safety and tuning checks. Ineligible bundles continue with ordinary decoding; see Speculative Decoding settings for the resolved reason. Sampling stays unchanged." + ), + kind: "segmented", + segments: nativeMTP.blocked ? [off] : [off, adaptive], + selected: selected, + on: nil, + // Off is the default, as the composer shows it. + explicit: selected != "off" + ) + } + + /// Stores a Native MTP choice through the composer's and Settings' one + /// route. A nil value resets to the default, Off. + @MainActor + static func applyNativeMTP(_ value: ModelOptionValue?, nativeMTP: NativeMTP?) async throws { + guard let nativeMTP else { throw ApplyError.unknownOption } + let segment = value?.stringValue ?? "off" + guard segment == "off" || (segment == "auto" && !nativeMTP.blocked) else { + throw ApplyError.invalidValue + } + var settings = ServerController.runtimeSettingsForConfigureTool().settings + settings.mtp.mode = segment == "off" ? .off : .auto + settings.mtp.draftTokenLimit = nil + settings.mtp.explicitDepth = nil + _ = await ServerController.applyRuntimeSettingsFromConfigureTool(settings) } /// Stores one choice the way the Mac composer's picker rows do. A nil diff --git a/Packages/OsaurusCore/Networking/HTTPHandler.swift b/Packages/OsaurusCore/Networking/HTTPHandler.swift index 82175d1ef..ff3b68fb9 100644 --- a/Packages/OsaurusCore/Networking/HTTPHandler.swift +++ b/Packages/OsaurusCore/Networking/HTTPHandler.swift @@ -5385,8 +5385,23 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { // Off-main bundle read, so local models report their real // thinking / effort contract instead of the cold-cache miss. _ = await LocalReasoningCapability.resolveForDispatch(modelId: request.model) - let outcome: (HTTPResponseStatus, String) = await MainActor.run { - if isWrite, let option = request.option { + // Native MTP is a server setting, written through its own async + // route rather than the per-model option store. + let nativeMTP = ModelOptionsSnapshot.NativeMTP.inspect(model: request.model) + let isNativeMTPWrite = isWrite && request.option == ModelOptionsSnapshot.nativeMTPOptionId + var nativeMTPFailure: (HTTPResponseStatus, String)? + if isNativeMTPWrite { + do { + try await ModelOptionsSnapshot.applyNativeMTP(request.value, nativeMTP: nativeMTP) + } catch ModelOptionsSnapshot.ApplyError.unknownOption { + nativeMTPFailure = (.notFound, #"{"error":"unknown_option"}"#) + } catch { + nativeMTPFailure = (.badRequest, #"{"error":"invalid_value"}"#) + } + } + let outcome: (HTTPResponseStatus, String) = await MainActor.run { [nativeMTPFailure] in + if let nativeMTPFailure { return nativeMTPFailure } + if isWrite, !isNativeMTPWrite, let option = request.option { do { try ModelOptionsSnapshot.apply(model: request.model, optionId: option, value: request.value) } catch ModelOptionsSnapshot.ApplyError.unknownOption { @@ -5395,7 +5410,7 @@ final class HTTPHandler: ChannelInboundHandler, Sendable { return (.badRequest, #"{"error":"invalid_value"}"#) } } - let snapshot = ModelOptionsSnapshot.make(for: request.model) + let snapshot = ModelOptionsSnapshot.make(for: request.model, nativeMTP: nativeMTP) guard let json = try? JSONEncoder.osaurusCanonical().encode(snapshot) else { return (.internalServerError, #"{"error":"encoding_failed"}"#) } diff --git a/docs/MOBILE_PROTOCOL.md b/docs/MOBILE_PROTOCOL.md index f28441396..cb5dbf93b 100644 --- a/docs/MOBILE_PROTOCOL.md +++ b/docs/MOBILE_PROTOCOL.md @@ -966,6 +966,11 @@ applies and nothing is sent to the model. Fields without a value (`icon`, stores one choice (`null` resets it) and answers with the same body as `POST`. `404 unknown_option`, `400 invalid_value`. +A local bundle with a native MTP head also gets a last `nativeMTPDepth` +option (Native MTP: `off` / `auto`, only `off` when its tuning blocks MTP). +Unlike the others it is the Mac's global Speculative Decoding setting, not a +per-model choice; setting it writes that setting, `null` meaning `off`. + ### 12.4 `PUT /models/favorites` `{"key":"","favorite":true}` adds the model to the From 22fa9de79d15d2adafc71cef860facc89417c71e Mon Sep 17 00:00:00 2001 From: RaajeevChandran Date: Tue, 6 Oct 2026 10:19:20 +0000 Subject: [PATCH 2/4] drop the native mtp help text from the phone's model options --- .../Models/Configuration/ModelOptionsSnapshot.swift | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Packages/OsaurusCore/Models/Configuration/ModelOptionsSnapshot.swift b/Packages/OsaurusCore/Models/Configuration/ModelOptionsSnapshot.swift index 02ac60ca4..29395b71e 100644 --- a/Packages/OsaurusCore/Models/Configuration/ModelOptionsSnapshot.swift +++ b/Packages/OsaurusCore/Models/Configuration/ModelOptionsSnapshot.swift @@ -174,13 +174,8 @@ struct ModelOptionsSnapshot: Encodable, Equatable { id: nativeMTPOptionId, label: L("Native MTP"), icon: "hare", - help: nativeMTP.blocked - ? L( - "Speculative decoding is disabled for this bundle because its MTP head is not safe for production use." - ) - : L( - "Off uses ordinary autoregressive decoding. On requests adaptive native MTP when the bundle passes runtime safety and tuning checks. Ineligible bundles continue with ordinary decoding; see Speculative Decoding settings for the resolved reason. Sampling stays unchanged." - ), + // The phone shows the choice alone, without the composer's copy. + help: nil, kind: "segmented", segments: nativeMTP.blocked ? [off] : [off, adaptive], selected: selected, From 9bb76a857a58419b26d4d1ffde999b3a0857781f Mon Sep 17 00:00:00 2001 From: RaajeevChandran Date: Tue, 6 Oct 2026 10:35:23 +0000 Subject: [PATCH 3/4] link osaurus in the mobile pairing intro to the testflight for people without the iphone app --- .../Resources/Localizable.xcstrings | 2 +- .../Views/Settings/OsaurusConnectView.swift | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Packages/OsaurusCore/Resources/Localizable.xcstrings b/Packages/OsaurusCore/Resources/Localizable.xcstrings index 018c7056b..a1be77c80 100644 --- a/Packages/OsaurusCore/Resources/Localizable.xcstrings +++ b/Packages/OsaurusCore/Resources/Localizable.xcstrings @@ -167002,7 +167002,7 @@ } } }, - "Open Osaurus on your iPhone, then generate a code here and type it in. Your iPhone must be on the same network as this Mac while pairing." : { + "Open [Osaurus](https://testflight.apple.com/join/yTF86bVb) on your iPhone, then generate a code here and type it in. Your iPhone must be on the same network as this Mac while pairing." : { }, "Open osaurus.ai/themes" : { diff --git a/Packages/OsaurusCore/Views/Settings/OsaurusConnectView.swift b/Packages/OsaurusCore/Views/Settings/OsaurusConnectView.swift index abb1b20c5..4ca0011cd 100644 --- a/Packages/OsaurusCore/Views/Settings/OsaurusConnectView.swift +++ b/Packages/OsaurusCore/Views/Settings/OsaurusConnectView.swift @@ -64,13 +64,25 @@ struct OsaurusConnectView: View { // MARK: Pairing + /// The pairing intro, "Osaurus" linking to the iPhone app's TestFlight + /// for anyone who has yet to install it, underlined like a web link. + private var pairingIntro: AttributedString { + var text = AttributedString( + localized: + "Open [Osaurus](https://testflight.apple.com/join/yTF86bVb) on your iPhone, then generate a code here and type it in. Your iPhone must be on the same network as this Mac while pairing.", + bundle: .module + ) + for run in text.runs where run.link != nil { + text[run.range].underlineStyle = .single + text[run.range].foregroundColor = theme.accentColor + } + return text + } + @ViewBuilder private var pairingSection: some View { SettingsSection(title: L("Pair an iPhone"), icon: "iphone.gen3") { VStack(alignment: .leading, spacing: 14) { - Text( - "Open Osaurus on your iPhone, then generate a code here and type it in. Your iPhone must be on the same network as this Mac while pairing.", - bundle: .module - ) + Text(pairingIntro) .font(.system(size: 12)) .foregroundColor(theme.secondaryText) From 62bdac92a543209eb0cceb99ee2bcb38ef034aac Mon Sep 17 00:00:00 2001 From: RaajeevChandran Date: Tue, 6 Oct 2026 10:35:41 +0000 Subject: [PATCH 4/4] say the iphone app is in testflight next to the pairing intro's link --- Packages/OsaurusCore/Resources/Localizable.xcstrings | 2 +- Packages/OsaurusCore/Views/Settings/OsaurusConnectView.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Packages/OsaurusCore/Resources/Localizable.xcstrings b/Packages/OsaurusCore/Resources/Localizable.xcstrings index a1be77c80..485426d30 100644 --- a/Packages/OsaurusCore/Resources/Localizable.xcstrings +++ b/Packages/OsaurusCore/Resources/Localizable.xcstrings @@ -167002,7 +167002,7 @@ } } }, - "Open [Osaurus](https://testflight.apple.com/join/yTF86bVb) on your iPhone, then generate a code here and type it in. Your iPhone must be on the same network as this Mac while pairing." : { + "Open [Osaurus](https://testflight.apple.com/join/yTF86bVb) (in TestFlight currently) on your iPhone, then generate a code here and type it in. Your iPhone must be on the same network as this Mac while pairing." : { }, "Open osaurus.ai/themes" : { diff --git a/Packages/OsaurusCore/Views/Settings/OsaurusConnectView.swift b/Packages/OsaurusCore/Views/Settings/OsaurusConnectView.swift index 4ca0011cd..7e4ae540c 100644 --- a/Packages/OsaurusCore/Views/Settings/OsaurusConnectView.swift +++ b/Packages/OsaurusCore/Views/Settings/OsaurusConnectView.swift @@ -69,7 +69,7 @@ struct OsaurusConnectView: View { private var pairingIntro: AttributedString { var text = AttributedString( localized: - "Open [Osaurus](https://testflight.apple.com/join/yTF86bVb) on your iPhone, then generate a code here and type it in. Your iPhone must be on the same network as this Mac while pairing.", + "Open [Osaurus](https://testflight.apple.com/join/yTF86bVb) (in TestFlight currently) on your iPhone, then generate a code here and type it in. Your iPhone must be on the same network as this Mac while pairing.", bundle: .module ) for run in text.runs where run.link != nil {