mana-swift-llm/CHANGELOG.md
till 5a25d44bd0 Release v0.4.0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 22:42:19 +02:00

9 KiB

Changelog

Alle Änderungen werden hier dokumentiert. Format orientiert an Keep a Changelog, Versionierung nach Semver.

[Unreleased]

[0.4.0] — 2026-06-10

Hinzugefügt — AI-Transparenz-Typ ManaAiMeta (ManaLLMShared)

Geteilter Wire-Format-Typ ManaAiMeta (Modell · Dienst · Dauer · Tokens · Mana · ≈ Strom) für die ökosystemweite KI-Transparenz-Story (mana/docs/AI_TRANSPARENCY.md). Pendant zu ManaAiMeta in @mana/shared-types/@mana/llm-client (Web). Eine Produkt-API liest die X-Mana-Ai-*-Header von mana-llm und reicht das Objekt an die App; die geteilte ManaAiActionMetaView (mana-swift-ui) rendert es.

  • ManaAiMeta in ManaLLMShared — Codable, Equatable, Sendable, public init. Schmale Lib, keine MLX-Dep.
  • Apps: kein Anpassungsbedarf. Optionaler Adoptionspfad für die ManaLLM-Facade-Apps (memoro/wordeck/pageta/…).

Hinzugefügt — BYOK TTS (Phase B, speak)

BYOK kann jetzt Audio aus Text erzeugen, symmetrisch zur Web-Seite (@mana/byok-providers speech.ts). Anders als Chat/Vision (SSE-Text) liefert TTS Binär-Audio.

  • ByokCapability.speak + TTS-Metadaten auf ByokProviderID (speechModels/voices/defaultSpeechModel/defaultVoice); speak in supportedCapabilities nur für OpenAI + Gemini.
  • ByokSpeech.swift — byokSynthesizeSpeech(_:apiKey:model:text:voice: format:)-Dispatcher. OpenAI (/v1/audio/speech) liefert ein fertiges Container-Format (Default mp3). Gemini (:generateContent, responseModalities:["AUDIO"]) liefert rohes 16-bit-PCM (audio/L16) → byokPCMToWav verpackt es zu abspielbarem WAV, byokParsePCMRate liest die Sample-Rate aus dem MIME.
  • ManaLLM.synthesizeSpeech(text:voice:model:provider:format:) — Facade- Verb, greift nur bei BYOK-Wahl + speak-fähigem Provider (sonst nil → App nutzt Server/mana-tts). provider wird als Wunsch an den Vault- Resolver durchgereicht (App-Picker).
  • 8 Tests (WAV-Header + Samples unangetastet, parsePCMRate, speak- Registry, Facade-Gating inkl. preferred-Provider), netz-/keychain-frei. swift test 47/47 grün.

Hinzugefügt — Nativer EXIF-Strip für Vision (Compliance)

  • ByokImagePrep.prepareForVision(_:maxEdge:quality:) — Swift-Pendant zu prepareImageForVision (web): re-enkodiert ein Bild über ImageIO, strippt alle Metadaten (EXIF inkl. GPS) und cappt die Kantenlänge (Default 2048). Re-Encode ohne Metadaten-Dict + …ThumbnailWithTransform (Orientierung in die Pixel) → kein Standort-/Orientation-Tag verlässt das Gerät. Pflicht vor describeImage (COMPLIANCE.md §5).
  • ManaLLM.describeImage(rawImageData:prompt:…) — Convenience, die den EXIF-Strip selbst macht; so kann ein Native-Konsument ihn nicht vergessen (analog Web describeImageStructured). 3 Tests (Downscale-Cap, Klein-Bild, Garbage→nil), netzwerk-/fixture-frei.

Hinzugefügt — BYOK Vision (Phase A, multimodal)

BYOK kann jetzt Bilder verstehen, nicht nur Text. Vision läuft bei allen vier Providern über denselben Chat-Endpoint + denselben Schlüssel — nur die Nachricht wird multimodal.

  • ByokContentPart (.text / .imageData(Data, mimeType:)) + multimodale ByokMessage (parts); der init(role:content:)-Text-Init bleibt back-compat (alle bestehenden Text-Pfade unverändert). textContent / hasImages-Helfer.
  • ByokCapability (textChat / vision; imageGen/transcribe/speak reserviert für Phase B/C) + ByokProviderID.supportedCapabilities / visionModels / defaultVisionModel. Alle vier können Vision.
  • Adapter serialisieren Bild-Parts pro Schema (OpenAI/Mistral image_url-Data-URI, Anthropic source.base64, Gemini inline_data). Reine Text-Encodings bleiben byte-gleich.
  • ManaLLM.describeImage(prompt:imageData:mimeType:…) — Vision-Verb der Facade. Greift nur bei BYOK-Wahl + vision-fähigem Provider; kein lokaler/Server-Fallback (On-Device-Vision experimentell) → nil ⇒ App rendert ihren Server-/metered-Pfad. ByokFacadeBox.selectedVision substituiert das Vision-Modell des Providers.
  • 9 neue, netzwerk-freie Tests (Encoding-Form + Capability-Registry + Vision-Gating). swift test 23/23 grün.

Datenschutz: Bilder sind sensibler als Text — Apps strippen EXIF + cappen die Kantenlänge vor dem Vision-Call (COMPLIANCE.md §5).

Hinzugefügt — BYOK in der ManaLLM-Facade

  • ManaLLM.configureByok(vault:defaultProvider:) — optionaler Boot- Call (neben configure()). Danach routen generate/summarize/ classify direkt zum Dritt-Anbieter, sobald der Nutzer in den Settings „Eigener Schlüssel (BYOK)" wählt (LLMBackendPreferenceStore.current == .byok) und ein Schlüssel im Vault liegt. Ohne den Call bleibt BYOK inert; ohne hinterlegten Schlüssel fällt die Facade still auf die lokale Backend-Priorität zurück. Der Default-ByokKeyVault() teilt die Keychain-Einträge mit der Settings-UI (ManaLLMSettingsState in mana-swift-ui).
  • Intern: ByokFacadeBox (NSLock-geschützter, @unchecked Sendable Resolver-Halter; selectedBackend(currentBackend:) ist rein/testbar). 4 neue Tests in ByokTests.

App-Adoption: ein Einzeiler ManaLLM.configureByok() im App-Boot (Facade-Apps: pageta / herbatrium / comicello). Apps mit eigenem LLMRouter (memoro) verdrahten setByokResolver(...) direkt.

[0.3.0] — 2026-06-04

Hinzugefügt — BYOK (Bring Your Own Key)

Neue Quelle LLMBackendID.byok: der Nutzer hinterlegt einen eigenen Dritt-API-Key (OpenAI / Anthropic / Gemini / Mistral); der Call geht direkt vom Gerät zum Anbieter — der Key berührt mana-Infrastruktur nie. Symmetrisch zum Web-Pendant @mana/browser-llm + @mana/byok-providers.

  • Sources/ManaLLM/Byok/:
    • ByokTypes.swift — ByokProviderID, ByokSelection, ByokMessage, ByokKeyResolver, ByokProvider-Protocol, ByokError.
    • ByokProviders.swift — vier URLSession-Streaming-Adapter (SSE), OpenAI/Mistral via gemeinsamen OpenAI-compat-Pfad, Anthropic + Gemini mit ihren eigenen Schemas.
    • ByokKeyVault.swift — Keychain-Speicher, bewusst app-privat, NICHT in group.ev.mana.session; keine Cross-Device-Sync. makeResolver(…).
    • ByokBackend.swift — LLMBackend-Konformität; ohne Resolver unavailable; Prompt/Key werden nie geloggt.
  • LLMRouter: setByokResolver(_:defaultProvider:), setAllowByokInPick(_:). BYOK wird nie still gewählt (Datenschutz-Disziplin, analog Web) — direkt nutzbar über backend(for: .byok).
  • LLMBackendID.byok.isOnDeviceLLM == false (Inhalt geht zu Dritt-SaaS).

Apps müssen anpassen: LLMBackendID hat einen neuen Case .byok — switch über alle Cases (z.B. in Settings-UI) muss ihn behandeln.

Compliance-Regel (sensible Inhalte per Default nicht über BYOK) siehe mana/docs/COMPLIANCE.md §5. 11 neue Tests (netzwerk-/keychain-frei), swift build + swift test grün (23 gesamt).

[0.2.0] — 2026-05-22

Minor — Preference-Stores nach ManaLLM geliftet + UI-Konsoldierung in mana-swift-ui möglich gemacht.

Hintergrund

Vorher lebten LLMBackendPreferenceStore und LLMDownloadOverCellularStore mit Memoro-spezifischen UserDefaults-Keys (memoro.llmBackend, memoro.llmDownloadOverCellular) ausschließlich in memoro-native. Damit die anderen ManaLLM-Konsumenten (Pageta, Comicello, Herbatrium) ebenfalls eine Settings-UI bekommen können (siehe mana-swift-ui 0.8.0 / ManaLLMUI), wandern die Stores hier ins Package und nutzen App-übergreifende Keys (mana.llm.*).

Neu

  • LLMBackendPreferenceStore — UserDefaults-Store für die On-Device- LLM-Wahl (mana.llm.backend). Default: .appleFM.
  • LLMDownloadOverCellularStore — WiFi-only-Default für Modell- Downloads (mana.llm.allowCellular). Default: false.
  • Beide Stores migrieren beim ersten Read einmalig aus Legacy-Keys:
    • memoro.llmBackend → mana.llm.backend
    • memoro.onDeviceLLMEnabled (alter Bool-Toggle) → mapped zu .appleFM / .noOp, dann mana.llm.backend
    • memoro.llmDownloadOverCellular → mana.llm.allowCellular
  • LLMBackend.removeCachedModel() — neue Protocol-Methode mit Default-No-Op-Impl. GemmaBackend überschreibt (löscht HF-Repo-Pfad im App-Group-Container). Apple FM und NoOp bleiben No-Op.
  • Test-Suite LLMPreferenceStoresTests (13 Tests, .serialized wegen UserDefaults.standard).

Geändert

  • GemmaBackend.removeCachedModel()-Signatur: throws → async throws (für Protocol-Konformität). Caller müssen try await schreiben statt try.

Migration für Apps

Apps die schon ManaLLM konsumieren brauchen nichts zu ändern — Symbol-Namen bleiben identisch, Defaults bleiben identisch, alte UserDefaults-Keys werden transparent migriert. Memoro hat seine lokalen Store-Duplikate gelöscht; der Build bleibt grün, weil Memoro import ManaLLM schon hatte.

Apps die heute keine LLM-Settings-UI haben können jetzt ManaLLMUI aus mana-swift-ui 0.8.0 einhängen (drop-in).