Behaviour for LLM clients used by the Interactive synthesizer.
chat/3 is the text contract: the model replies with a JSON ActionPlan
envelope as text, which the synthesizer parses.
chat_with_native_tools/4 is the optional native contract: tool
signatures are passed to the provider as structured tools, and the model
replies with validated tool_use blocks instead of a text envelope —
removing the whole class of text-parse failures (smart quotes, dotted names,
over-structured values). An adapter opts in simply by implementing it; the
synthesizer auto-selects the native path when the actor's strategy_config
sets tool_mode: :native and the configured client exports
chat_with_native_tools/4. Return shape:
{:ok, %{
tool_calls: [%{name: "tool__action", input: %{...}}], # [] if none
text: "assistant prose, if any",
model: "model-id", # optional
usage: %{...} # optional
}}