StructuredOutput
Turns "parse a typed T out of a model's free-text reply" from a hand-rolled regex scrape (the kind that breaks the first time a model adds a newline or an explanatory sentence) into one shared, tested path: a schema hint embedded in the prompt, a tolerant JSON parse of the reply, one repair retry that shows the model its own unparseable output, and a typed AiFailure otherwise.
Works with either AI seam — pass onDeviceLlm::generate or an :llm-chat AiProvider ({ prompt -> provider.complete(listOf(AiMessage(AiMessage.Role.USER, prompt))).map { ... } }), since both already speak AiResult. PromptGuard, if the caller wants it, runs inside whichever generate implementation the caller supplies — this class only owns the schema/parse/retry loop, not prompt safety.
Functions
Runs prompt (with a schema hint appended) through generate. Parses the reply as T; on a malformed reply, retries once with the bad reply and a "fix your JSON" nudge appended. A failure from generate itself (network, timeout, no key) is not a parse problem and is returned as-is with no retry — retrying a call that already told you why it failed just spends another round trip for the same answer.