Package-level declarations
Types
Honest, machine-readable answer to "what can this AI backend actually do right now" — the counterpart to a bare isAvailable(): Boolean that collapses "streams tokens", "accepts images" and "why is this off" into a single true/false a caller can't act on or show to a user. Lives in :result next to AiFailure for the same reason: the on-device (:ai) and cloud (:llm-chat) seams both report through it rather than inventing their own descriptor shape.
Why an AI call (com.siddharth.kmp.llmchat.AiProvider.complete or on-device OnDeviceLlm.generate) didn't produce text. Lives in :result — a zero-dependency module both the cloud (:llm-chat) and on-device (:ai) seams already sit downstream of conceptually — so neither invents its own error type and callers handling both seams share one when.
Root of the typed error hierarchy carried in Result.Failure. Split by source so callers can react differently to a network failure vs a local one. Apps extend this with their own arms (e.g. a Validation error) by implementing the interface.
One prompt-injection guard reused at both AI seams — on-device (:ai's CompositeOnDeviceLlm) and cloud (:llm-chat's buildProviderChain) — so a receipt, job description, or chat message that says "ignore previous instructions" is treated as inert data by every app in the family, not just the careful ones. Lives in :result, not either seam module: :ai has no wasmJs target but :llm-chat does, so a shared helper has to sit somewhere both already depend on — same reason AiResult/AiFailure live here instead of being duplicated per seam.
A typed success-or-failure. Unlike kotlin.Result, the error arm is a typed E (usually a DataError) rather than a Throwable, so callers exhaustively handle known failure modes instead of catching. Import this Result explicitly where kotlin.Result is also in scope.