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.
ManaAiMetainManaLLMShared—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 aufByokProviderID(speechModels/voices/defaultSpeechModel/defaultVoice);speakinsupportedCapabilitiesnur 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) →byokPCMToWavverpackt es zu abspielbarem WAV,byokParsePCMRateliest die Sample-Rate aus dem MIME.ManaLLM.synthesizeSpeech(text:voice:model:provider:format:)— Facade- Verb, greift nur bei BYOK-Wahl +speak-fähigem Provider (sonstnil→ App nutzt Server/mana-tts).providerwird 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 test47/47 grün.
Hinzugefügt — Nativer EXIF-Strip für Vision (Compliance)
ByokImagePrep.prepareForVision(_:maxEdge:quality:)— Swift-Pendant zuprepareImageForVision(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 vordescribeImage(COMPLIANCE.md §5).ManaLLM.describeImage(rawImageData:prompt:…)— Convenience, die den EXIF-Strip selbst macht; so kann ein Native-Konsument ihn nicht vergessen (analog WebdescribeImageStructured). 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:)) + multimodaleByokMessage(parts); derinit(role:content:)-Text-Init bleibt back-compat (alle bestehenden Text-Pfade unverändert).textContent/hasImages-Helfer.ByokCapability(textChat/vision;imageGen/transcribe/speakreserviert 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, Anthropicsource.base64, Geminiinline_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.selectedVisionsubstituiert das Vision-Modell des Providers.- 9 neue, netzwerk-freie Tests (Encoding-Form + Capability-Registry +
Vision-Gating).
swift test23/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 (nebenconfigure()). Danach routengenerate/summarize/classifydirekt 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 (ManaLLMSettingsStatein mana-swift-ui).- Intern:
ByokFacadeBox(NSLock-geschützter,@unchecked SendableResolver-Halter;selectedBackend(currentBackend:)ist rein/testbar). 4 neue Tests inByokTests.
App-Adoption: ein Einzeiler
ManaLLM.configureByok()im App-Boot (Facade-Apps: pageta / herbatrium / comicello). Apps mit eigenemLLMRouter(memoro) verdrahtensetByokResolver(...)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— vierURLSession-Streaming-Adapter (SSE), OpenAI/Mistral via gemeinsamen OpenAI-compat-Pfad, Anthropic + Gemini mit ihren eigenen Schemas.ByokKeyVault.swift— Keychain-Speicher, bewusst app-privat, NICHT ingroup.ev.mana.session; keine Cross-Device-Sync.makeResolver(…).ByokBackend.swift—LLMBackend-Konformität; ohne Resolverunavailable; Prompt/Key werden nie geloggt.
LLMRouter:setByokResolver(_:defaultProvider:),setAllowByokInPick(_:). BYOK wird nie still gewählt (Datenschutz-Disziplin, analog Web) — direkt nutzbar überbackend(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.backendmemoro.onDeviceLLMEnabled(alter Bool-Toggle) → mapped zu.appleFM/.noOp, dannmana.llm.backendmemoro.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,.serializedwegenUserDefaults.standard).
Geändert
GemmaBackend.removeCachedModel()-Signatur:throws→async throws(für Protocol-Konformität). Caller müssentry awaitschreiben statttry.
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).