|
wxMaxima
|
Talks to one external AI provider's chat completion HTTP API. More...
#include <AiProvider.h>
Public Member Functions | |
| AiProvider (wxString baseUrl, wxString apiKey, wxString model) | |
| virtual AiProviderKind | Kind () const =0 |
| wxString | Name () const |
| AiProviderKindName(Kind()) for a built-in provider, or the user's own chosen name for a custom one (Kind() == Custom doesn't carry a name by itself) – SetDisplayName() is called by MakeAiProviderForShape() for exactly this reason. | |
| void | SetDisplayName (const wxString &name) |
| void | SetBasicAuthUser (const wxString &user) |
Makes this provider authenticate with HTTP Basic instead of its own credential header, using user and the API key as the password. | |
| virtual wxString | RequestUrl () const |
| The full URL SendChat() will POST to. Virtual so Google's provider can append the model id (its endpoint is per-model, unlike the others). | |
| virtual std::vector< std::pair< wxString, wxString > > | AuthHeaders () const =0 |
| Any additional headers this provider's auth scheme needs, name/value pairs, beyond the "POST JSON" content-type SendChat() always sets. | |
| virtual wxString | BuildRequestBody (const wxString &context, const std::vector< AiChatMessage > &history) const =0 |
| Builds the JSON request body for one chat turn. | |
| virtual wxString | ParseReply (const wxString &responseBody) const =0 |
| Extracts the assistant's reply text from a successful (2xx) response body. | |
| virtual wxString | ModelsRequestUrl () const |
| The URL FetchModels() asks for this provider's current model ids, or an empty string if there is nothing sensible to ask. | |
| virtual std::vector< wxString > | ParseModelList (const wxString &responseBody) const |
| The model ids in a successful (2xx) model-list response, most recently created first where the provider says so. | |
Static Public Member Functions | |
| static bool | SecretStoreAvailable () |
| True if this build of wxWidgets has a working wxSecretStore backend (>= 3.1.1, compiled with wxUSE_SECRETSTORE, AND an actual OS keyring service reachable at runtime – e.g. gnome-keyring/kwallet on Linux, always true on Windows/macOS). API keys are only ever stored here, never in plain Configuration/wxConfig, so the whole AI Chat feature (Options tab, sidebar, menu entry) stays hidden whenever this is false rather than falling back to storing a key in plain text. | |
| static void | SaveApiKey (const wxString &service, const wxString &apiKey) |
Saves (or, for an empty key, deletes) an API key for service – a stable identifier: AiProviderKindName(kind) for a built-in provider, or CustomProviderSecretService(id) for a custom one. No-op (and never called by any code path that matters) if !SecretStoreAvailable(). | |
| static wxString | LoadApiKey (const wxString &service) |
Returns the stored key for service, or an empty string if none is stored (also the result if !SecretStoreAvailable() – there is nowhere a key could have been saved to). | |
| static void | DeleteApiKey (const wxString &service) |
| static wxString | CustomProviderSecretService (const wxString &id) |
| The secret-store service name for a custom provider's own id – kept distinct from a bare built-in kind name so a custom provider a user happens to name e.g. "OpenAI" can never collide with the real built-in OpenAI entry's stored key. | |
| static wxString | BuiltinProviderSecretService (AiProviderKind kind) |
| The secret-store service name for one of the four built-in kinds – just AiProviderKindName(kind), given its own name so every call site that needs it (Configuration's four AiApiKeyX() accessors, the plain-text-key migration in Configuration::ReadConfig()) shares one definition instead of separately hardcoding the same string. | |
| static AiRequestCanceller | SendChat (std::shared_ptr< const AiProvider > self, wxEvtHandler *owner, const wxString &context, const std::vector< AiChatMessage > &history, std::function< void(bool ok, const wxString &replyOrError)> callback, std::function< void(const wxString &requestBody)> onRequest=nullptr, std::function< void(bool ok, const wxString &responseBodyOrDetail)> onResponse=nullptr) |
Sends one chat turn asynchronously via wxWebRequest and calls callback(ok, replyOrError) exactly once, on the GUI thread, once the HTTP request completes, fails, or errors out. | |
| static void | FetchModels (std::shared_ptr< const AiProvider > self, wxEvtHandler *owner, std::function< void(bool ok, const std::vector< wxString > &models, const wxString &errorIfAny)> callback) |
Asks the provider which models it offers, asynchronously, calling callback exactly once on the GUI thread. | |
| static bool | NetworkingAvailable () |
| True if this build of wxWidgets has wxWebRequest at all (>= 3.1.5, built with a working backend) – if false, the whole chat sidebar feature has nothing to talk to and should say so rather than silently failing every request. | |
Protected Member Functions | |
| bool | UsesBasicAuth () const |
| True when SetBasicAuthUser() was given a non-empty name, i.e. every AuthHeaders() override should send BasicAuthHeader() and omit its own credential header – there is only one Authorization slot, and only one stored secret to put in it. | |
| std::pair< wxString, wxString > | BasicAuthHeader () const |
| "Authorization: Basic base64(user:password)", with the API key as the password. Only meaningful while UsesBasicAuth() is true. | |
Protected Attributes | |
| wxString | m_baseUrl |
| wxString | m_apiKey |
| wxString | m_model |
| wxString | m_displayName |
| wxString | m_basicAuthUser |
Talks to one external AI provider's chat completion HTTP API.
Deliberately split into a stateless, directly-testable half (BuildRequestBody()/ ParseReply(), pure string/JSON in and out, no networking – see test/unit_tests/test_AiProvider.cpp) and the actual network call (SendChat()), which needs a live wxWebRequest and can only really be verified against a real or fake HTTP server (done live, see AGENTS.md).
|
pure virtual |
Builds the JSON request body for one chat turn.
context is a read-only worksheet snapshot (from McpTools), sent as a system/context message ahead of the actual conversation; history is every prior turn plus the new user message as its last entry. Pure function, no I/O – unit-tested directly.
|
static |
Asks the provider which models it offers, asynchronously, calling callback exactly once on the GUI thread.
Same lifetime rules as SendChat(), and for the same reasons: self is an explicit shared_ptr so the instance that built the request survives until the response arrives, and owner must outlive the request. A provider whose ModelsRequestUrl() is empty calls back immediately with ok=false rather than pretending to ask.
|
virtual |
The URL FetchModels() asks for this provider's current model ids, or an empty string if there is nothing sensible to ask.
Derived from the chat endpoint the user already configured rather than stored separately, because the two are the same API and a second field would be one more thing to get out of step. The default implementation is the OpenAI-compatible rule – drop a trailing "/chat/completions", append "/models" – which is what makes this work for providers this codebase has never heard of: "GET <base>/v1/models" is the de facto standard that the same providers implementing OpenAI's chat shape (OpenRouter, Groq, Together, vLLM, LM Studio, llama.cpp server, Ollama's compatibility layer, ...) implement alongside it. Anthropic and Google override it for their own spellings.
|
virtual |
The model ids in a successful (2xx) model-list response, most recently created first where the provider says so.
Pure function, no I/O, unit-tested directly – same split as BuildRequestBody()/ParseReply(). Throws AiProviderError if the body doesn't parse or has no model list in it at all; silently skips an individual entry it cannot read, since one odd entry is no reason to discard an otherwise usable list.
|
pure virtual |
Extracts the assistant's reply text from a successful (2xx) response body.
Throws AiProviderError (see below) if the body doesn't parse or doesn't have the shape this provider expects. Pure function, no I/O.
|
static |
Sends one chat turn asynchronously via wxWebRequest and calls callback(ok, replyOrError) exactly once, on the GUI thread, once the HTTP request completes, fails, or errors out.
owner is the wxEvtHandler the completion event is delivered to – must outlive the request; the sidebar itself is the intended owner. No-op (calls back with ok=false immediately) if wxUSE_WEBREQUEST is off in this build.
Static, taking self as an explicit shared_ptr rather than being a plain instance method: the completion lambda needs the provider object (for ParseReply()/Name(), both virtual) to still exist whenever the response actually arrives, which can outlast the caller doing something else entirely – e.g. the user changing the configured provider/model in Options while a request is still in flight. Capturing a shared_ptr keeps the exact instance that sent the request alive for the callback regardless of what AiChatSidebar does to its own current-provider pointer in the meantime; capturing a raw this would not.
Returns an AiRequestCanceller for this one request (see there), so the caller can interrupt a reply that is taking too long, or one it no longer wants. Cancelling still goes through callback exactly once, like every other way a request can end.
|
inline |
Makes this provider authenticate with HTTP Basic instead of its own credential header, using user and the API key as the password.
Set for a custom provider whose endpoint sits behind a Basic-auth reverse proxy. Leaving it empty (the default) keeps every provider's native header exactly as it was, so this cannot affect a built-in.