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). The synthesizer uses the native path by default
whenever the configured client exports chat_with_native_tools/4; an actor
opts out with strategy_config: %{tool_mode: :text}, and a client that
doesn't implement the callback falls back to the text path automatically.
Return shape:
{:ok, %{
tool_calls: [%{name: "tool__action", input: %{...}}], # [] if none
text: "assistant prose, if any",
model: "model-id", # optional
usage: %{...} # optional
}}