Vendor-neutral AI broker. One implementation per provider (Claude,
OpenAI, Gemini). Callers depend on this interface; the AiBrokerRegistry
is the indirection that lets the active provider change at runtime.
What every broker call boils down to. system is the persistent
instruction; messages is the turn history (oldest first). Both
complete* and chat* build the same wire payload — complete*
is just chat* with one user message.
Google Gemini (generateContent / streamGenerateContent).
Pagination on listModels caps at 5 pages × 50; only gemini-* ids
that support generateContent are kept.
Where API keys come from. Flutter apps with secure storage implement
this against their own store; CLI / backend code can use the
built-in EnvKeyResolver or MapKeyResolver.
One parsed Server-Sent Event. OpenAI and Anthropic both use SSE for
their streaming endpoints (Gemini uses a JSON-array stream — see
the Gemini broker for that codepath).
Who authored a message in a chat. system lives on
ChatRequest.system, not here — every provider treats system
instructions specially (Anthropic puts them at top-level; Gemini
uses systemInstruction; OpenAI uses a role:system message). We
only model the back-and-forth.
Status codes that mean "try again later, the request itself is
fine": rate limits and transient upstream overload. Quota-exceeded
(a hard 429 with quota in the body) is not retryable — callers
should surface that to the user instead of burning attempts.
Decodes a UTF-8 byte stream into SseEvents. Buffers across chunk
boundaries (one event may span several Stream<List<int>> chunks),
joins multi-line data: payloads with \n per the SSE spec, and
filters out keep-alive comments.
Opens a POST request and returns the response body as a byte
stream. Throws AiBrokerException on non-2xx (the body is buffered
in that path so we can include the error message).
HTTP retry with exponential backoff capped at 5 attempts × 5s × 2ⁿ.
Matches the loop used in chitbot's OpenAiBot / GeminiBot /
ClaudeClient, generalised so all three brokers share one path.
Strips a leading and trailing markdown code fence from text. Model
output for "give me code" prompts usually arrives wrapped in
```lang\n...\n```, even after an explicit "code only"
instruction. Call this on the broker's raw text before writing it to
a .dart / .ts / etc. file.