|
wxMaxima
|
Talks to one of a handful of external AI chat APIs for the AI chat sidebar (AiChatSidebar). More...
#include "precomp.h"#include <wx/wx.h>#include <functional>#include <memory>#include <vector>Go to the source code of this file.
Data Structures | |
| struct | AiChatMessage |
| One turn of a chat conversation. More... | |
| struct | AiCustomProviderConfig |
| One user-added custom provider, as persisted in Configuration's AiCustomProvidersJson() (everything except the API key, which lives in the OS secret store, keyed by CustomProviderSecretService(id) – see AiProvider::SaveApiKey()/LoadApiKey()). More... | |
| struct | AiLocalServerPreset |
| One well-known local AI server's connection defaults, offered as a quick-fill preset in the "Add custom provider" dialog (Options -> AI Chat) – picking one just pre-fills that dialog's own editable fields, it's a shortcut for not typing e.g. Ollama's default port from memory, not a fifth built-in AiProviderKind. All of these speak the OpenAI-compatible shape and normally don't check the API key at all (it can be left blank), since a local server has no third party to authenticate to. More... | |
| class | AiProvider |
| Talks to one external AI provider's chat completion HTTP API. More... | |
| class | AiProviderError |
| Thrown by ParseReply() for a response that doesn't parse or doesn't have the expected shape – distinct from a non-2xx HTTP status, which SendChat() reports directly from the response body/status line instead. More... | |
Typedefs | |
| using | AiRequestCanceller = std::function< void()> |
| Cancels the one chat request it was handed out for, and does nothing at all once that request has finished. | |
Enumerations | |
| enum class | AiProviderKind { None = 0 , Anthropic = 1 , OpenAI = 2 , Google = 3 , Qwen = 4 , Custom = 5 , GitHubModels = 6 , DeepSeek = 7 , OpenRouter = 8 } |
| Which AI service to talk to. Persisted as a plain int in Configuration; keep existing values stable across releases (append only). Custom is what every user-added provider (see AiCustomProviderConfig) reports from AiProvider::Kind() – Configuration::AiActiveCustomProviderId() says which one, since a plain kind alone can't distinguish two custom entries from each other. GitHubModels is GitHub's own official, OpenAI-compatible model-hosting API (https://github.com/marketplace/models), authenticated with a plain GitHub personal access token – deliberately NOT "GitHub Copilot Chat": that assistant has no sanctioned third-party API at all (the community tools that reach it do so by reusing an approved editor's OAuth client id and calling an undocumented internal endpoint, which risks the account being flagged under GitHub's Copilot terms), so it was not implemented here – see the "GitHub Models" follow-up in AGENTS.md's AI chat sidebar entry for the full reasoning. DeepSeek and OpenRouter are both plain OpenAI-compatible APIs with a pasted key; OpenRouter is itself a router in front of many other providers' models, picked by a "<publisher>/<model>" name. | |
| enum class | AiProviderShape { Anthropic , OpenAiCompatible , Google } |
| Which request/response wire format a provider uses. The four built-in AiProviderKinds each hardcode one of these; a user-added custom provider (AiCustomProviderConfig) picks one explicitly, since these three cover every shape actually implemented – Anthropic's and Google's own APIs, plus the "OpenAI-compatible" shape a large number of real third-party and self-hosted endpoints (OpenRouter, Groq, Ollama, ...) already speak verbatim, precisely because it's become a de facto standard. None of the three auth conventions that go with these shapes (a plain x-api-key header, "Authorization: Bearer <key>", or x-goog-api-key) are exotic enough to need a fourth, freeform "custom auth" option – picking a shape already picks its auth convention too. | |
Functions | |
| wxString | AiProviderShapeName (AiProviderShape shape) |
| Human-readable name for the shape picker in the "Add custom provider" dialog. | |
| int | AiProviderShapeToChoiceIndex (AiProviderShape shape) |
| The two halves of the shape picker's index <-> enum mapping. | |
| AiProviderShape | AiProviderShapeFromChoiceIndex (int index) |
| wxString | AiProviderRequestUrlProblem (const wxString &url) |
Empty if url is usable as a custom provider's request URL; otherwise a short, user-facing explanation of what is wrong with it. | |
| std::vector< AiLocalServerPreset > | AiKnownLocalServerPresets () |
| A short, hand-picked list of common local LLM servers – not exhaustive, just the ones popular enough to be worth a one-click shortcut; anything else is still just as reachable via a plain custom entry with a hand-typed URL. | |
| wxString | NewAiCustomProviderId () |
| A fresh id for a new custom provider (see AiCustomProviderConfig::id) – never shown to the user, just needs to not collide with any existing one for the lifetime of this installation. | |
| std::vector< AiCustomProviderConfig > | ParseAiCustomProviders (const wxString &json) |
| Parses Configuration::AiCustomProvidersJson() back into a list; returns an empty list for an empty or malformed string rather than throwing – a corrupted/hand-edited config file should degrade to "no custom
providers configured", not crash Options on open. | |
| wxString | SerializeAiCustomProviders (const std::vector< AiCustomProviderConfig > &providers) |
| The inverse of ParseAiCustomProviders(), for Configuration:: AiCustomProvidersJson()'s setter. | |
| wxString | AiProviderKindName (AiProviderKind kind) |
| Human-readable name for Options/the sidebar's status line. | |
| wxString | AiProviderDefaultModel (AiProviderKind kind) |
| This provider's built-in default model id, shown as Options' initial value for a not-yet-configured model field. Users can override it – model catalogs change far more often than this code does. | |
| wxString | AiProviderApiKeyUrl (AiProviderKind kind) |
| Where to go to create/find an API key for this provider – shown as a link next to that provider's key field in Options, since there is no "log in" button that could get one automatically: none of these five providers offer a legitimate third-party OAuth flow a desktop app could use, so pasting a key from the provider's own site is the only option. Best-effort: a provider's console is free to move its own pages, same caveat as AiProviderDefaultModel()'s model ids going stale over time. | |
| wxString | AiProviderBaseUrl (AiProviderKind kind) |
| A built-in provider's fixed request URL (Google's own, without the "<model>:generateContent" suffix its real RequestUrl() appends – see GoogleProvider), shown read-only in Options next to a Custom entry's editable one, so both look like the same kind of field even though only one of them can actually be changed. Must be kept in sync with MakeAiProvider()'s own hardcoded URLs; there is deliberately no single shared source for both, since MakeAiProvider() builds a real (kind, shape)-bound provider object while this is a pure display string with no such object to read it back from. | |
| wxString | AiProviderModelListUrl (AiProviderKind kind) |
| Where to see this provider's current list of available model ids – shown as a link next to Options' model field. AiProviderDefaultModel() uses each provider's own "rolling" alias where one exists, but a model line still eventually gets superseded by a new one, something a plain string constant in this codebase cannot track by itself; a link to the provider's own list is the durable fix, not a fancier auto-detection mechanism that would itself need to keep up with each provider's API just to answer the same question this link answers directly. Same best-effort/link-rot caveat as AiProviderApiKeyUrl(). | |
| std::shared_ptr< AiProvider > | MakeAiProvider (AiProviderKind kind, const wxString &apiKey, const wxString &model) |
| Builds the provider for this kind, or nullptr for AiProviderKind::None. Never called with AiProviderKind::Custom – see MakeAiProviderForShape() for that case, since a custom provider's URL/shape isn't implied by the kind alone. | |
| std::shared_ptr< AiProvider > | MakeAiProviderForShape (AiProviderShape shape, const wxString &displayName, const wxString &baseUrl, const wxString &apiKey, const wxString &model, const wxString &basicAuthUser=wxEmptyString) |
| Builds a provider for a user-added custom entry: reuses the exact same request/response-shape implementation a built-in provider of that shape uses (AnthropicProvider/OpenAiCompatibleProvider/GoogleProvider), just pointed at an arbitrary URL with the user's own display name and model instead of one of the four hardcoded built-ins. Kind() on the result is always AiProviderKind::Custom. | |
Talks to one of a handful of external AI chat APIs for the AI chat sidebar (AiChatSidebar).
See that class for the safety scope (read-only worksheet context, no tool-calling in this first pass).
Every provider here needs an internet connection to a third-party service and an API key the user pastes into Options – unlike the MCP server (src/mcp/), which is entirely local and needs neither. The two features are otherwise unrelated beyond both ultimately drawing on McpTools for worksheet context.
| using AiRequestCanceller = std::function<void()> |
Cancels the one chat request it was handed out for, and does nothing at all once that request has finished.
AiProvider::SendChat() returns one of these so the chat sidebar's "Interrupt" button has something to interrupt: only the wxWebRequest can cancel a request, it is created inside SendChat(), and – being a ref-counted handle nothing else keeps – is unreachable from outside otherwise. Empty (i.e. compares false) when there is nothing that could be cancelled: a build without wxUSE_WEBREQUEST, or a request that could not even be created.
Calling it ends the request in wxWebRequest::State_Cancelled, which reaches the caller as one ordinary callback(false, ...) – a cancelled request reports through exactly the same "callback fires once, whatever the outcome" contract as every other ending, never as a second callback and never as silence. Calling it after the request already finished is safe and does nothing, so a caller that holds one a little too long cannot cancel somebody else's request by accident.
| wxString AiProviderRequestUrlProblem | ( | const wxString & | url | ) |
Empty if url is usable as a custom provider's request URL; otherwise a short, user-facing explanation of what is wrong with it.
Exists because the one thing that actually goes wrong here is easy to type and impossible to diagnose from the resulting failure: a bare "127.0.0.1:11434" (host and port, no scheme and no path) is a perfectly natural thing to write for a local server, but it is not a URL – everything before the first colon is the scheme, so the backend is asked for a "127.0.0.1" protocol it has never heard of and the whole thing surfaces much later as an unexplained network failure. Catching it at the point it is typed turns that into one sentence saying what to fix.
Deliberately does NOT guess a missing scheme: prepending "http://" to a remote host would send the user's API key over the wire in clear text, and prepending "https://" to a local server that only speaks plain HTTP just trades one confusing failure for another. Asking is cheap; guessing wrong here is not.
| int AiProviderShapeToChoiceIndex | ( | AiProviderShape | shape | ) |
The two halves of the shape picker's index <-> enum mapping.
The dropdown deliberately lists OpenAiCompatible first (by far the most common choice for a hand-added endpoint, and the one every local-server preset uses), which is not the order AiProviderShape itself declares – so a plain static_cast<int>(shape) is NOT a valid selection index, and a plain static_cast<AiProviderShape>(selection) is NOT a valid shape. Every conversion must go through these two, or a custom provider silently changes its own wire format behind the user's back: populating the picker with a raw cast showed a freshly-added OpenAI-compatible entry as "Anthropic (Messages API)", and Options' own save-before-switch write-back then really did turn it into one.