186 lines
9 KiB
Markdown
186 lines
9 KiB
Markdown
# Changelog
|
|
|
|
Alle Änderungen werden hier dokumentiert. Format orientiert an
|
|
[Keep a Changelog](https://keepachangelog.com), Versionierung nach
|
|
[Semver](https://semver.org).
|
|
|
|
## [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).
|