Skip to content

feat(app): ✨ adding Preferences/Wingman for ollama, OpenRouter and OpenAI#1558

Open
dvorka wants to merge 29 commits into
enh-1539/ollama-llm-choicefrom
enh-1539/ollama-llm-choice-ii-vibe
Open

feat(app): ✨ adding Preferences/Wingman for ollama, OpenRouter and OpenAI#1558
dvorka wants to merge 29 commits into
enh-1539/ollama-llm-choicefrom
enh-1539/ollama-llm-choice-ii-vibe

Conversation

@dvorka
Copy link
Copy Markdown
Collaborator

@dvorka dvorka commented Feb 8, 2026

Tasks:

  • green Linux build
  • green Linux gtest based tests
  • green macOS build
  • green Win build

This PR brings:

  • ollama support & configuration.
  • OpenRouter support & configuration.
  • rewritten OpenAI configuration.

Related:

@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented Feb 8, 2026

🤖 Augment PR Summary

Summary: Adds a new “Wingman2” Preferences tab for managing multiple LLM provider profiles (OpenAI/ollama).
Changes:

  • Registers new dialog sources/headers in app/app.pro.
  • Implements Wingman2Tab UI with provider dropdown, details panel, and Add/Edit/Remove/Test actions.
  • Adds dialogs to select a provider type and configure OpenAI/ollama settings (API key/URL + model selection).
  • Extends Configuration with LlmProviderConfig, provider collection APIs, active-provider tracking, and a legacy-migration helper.
  • Fixes OllamaWingman::listModels() to store the parsed model name.
  • Enhances OpenAiWingman::listModels() with an HTTP GET to /v1/models and fallback defaults.
  • Adds a detailed design doc for Wingman2 LLM configuration.
Technical Notes: Provider “probe” routines currently do basic validation and mark configs as valid/invalid for UI feedback.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 7 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.


#include <QtWidgets>

#include "../../lib/src/config/configuration.h"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The relative include ../../lib/src/config/configuration.h resolves to app/src/lib/... from this header, which doesn’t exist in the repo, so these new dialogs likely won’t compile. Same issue also appears in the OpenAI/ollama config dialog headers.

Severity: high

Other Locations
  • app/src/qt/dialogs/openai_config_dialog.h:25
  • app/src/qt/dialogs/openai_config_dialog.h:26
  • app/src/qt/dialogs/ollama_config_dialog.h:25
  • app/src/qt/dialogs/ollama_config_dialog.h:26

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

QPushButton* addProviderButton;

QGroupBox* providerDetailsGroup;
QLabel* providerTypeLabel;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wingman2Tab declares providerTypeLabel/modelLabel/statusLabel members, but the .cpp constructs local QLabel*s instead; these members remain uninitialized. This is easy to accidentally dereference later and crash.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

: QWidget(parent),
config(Configuration::getInstance())
{
helpLabel = new QLabel(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The help text says Wingman2 config is “to be used by Wingman”, but the current runtime initialization path still appears to read only the legacy wingmanProvider/wingmanOpenAi*/wingmanOllama* fields. This may make the new tab misleading if Wingman doesn’t actually consume llmProviders yet.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

// populate providers combo
llmProvidersCombo->clear();

vector<LlmProviderConfig>& providers = config.getLlmProviders();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wingman2Tab::refresh() populates from config.getLlmProviders() directly; since migrateFromLegacyWingmanConfig() isn’t invoked here, existing Wingman users may see an empty provider list even if legacy config is set.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

if (providerType == WINGMAN_PROVIDER_OPENAI) {
OpenAiConfigDialog configDialog(this);
if (configDialog.exec() == QDialog::Accepted) {
config.addLlmProvider(configDialog.getProviderConfig());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Providers added via config.addLlmProvider() look to be stored only in-memory; MarkdownConfigurationRepresentation currently persists only the legacy Wingman fields. That means Wingman2 providers/activeLlmProviderId may be lost after restart.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread app/src/qt/dialogs/openai_config_dialog.cpp Outdated
Comment thread app/src/qt/dialogs/configuration_dialog.cpp Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new “Wingman2” preferences workflow to manage multiple LLM provider configurations (OpenAI + ollama), alongside groundwork in Configuration for storing providers, plus improvements to model discovery and a small ollama bug fix.

Changes:

  • Introduces Wingman2 UI (tab + dialogs) to add/configure LLM providers and select an active provider.
  • Extends Configuration with a LlmProviderConfig model and basic CRUD/probe/migration APIs.
  • Enhances OpenAI model listing via /v1/models and fixes an ollama model-list parsing bug.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 22 comments.

Show a summary per file
File Description
vibe/designs/wingman2-llm-configuration.md Design spec for Wingman2 provider configuration UX, storage, and probing.
lib/src/mind/ai/llm/openai_wingman.h Adds declaration for HTTP model listing helper.
lib/src/mind/ai/llm/openai_wingman.cpp Implements OpenAI /v1/models model fetching and fallback defaults.
lib/src/mind/ai/llm/ollama_wingman.cpp Fixes model name push bug in model listing.
lib/src/config/configuration.h Adds Wingman2 provider config struct, defaults, and new public APIs.
lib/src/config/configuration.cpp Implements provider CRUD, active selection, probe stubs, and migration helper.
app/src/qt/dialogs/configuration_dialog.h Adds Wingman2Tab declaration and members.
app/src/qt/dialogs/configuration_dialog.cpp Adds Wingman2 tab UI/handlers and wires it into Preferences show/save.
app/src/qt/dialogs/openai_config_dialog.{h,cpp} New OpenAI provider dialog (API key + model + refresh/probe/add).
app/src/qt/dialogs/ollama_config_dialog.{h,cpp} New ollama provider dialog (URL + model + refresh/probe/add).
app/src/qt/dialogs/add_llm_provider_dialog.{h,cpp} New dialog to choose provider type before configuring.
app/app.pro Registers the new dialog sources/headers with qmake.
lib/src/app_info.h Updates legal copyright year range.
build/ubuntu/debian/copyright Updates Debian packaging copyright year range.
build/debian/debian/copyright Updates Debian packaging copyright year range.
.github/copilot-instructions.md Adds repository-specific Copilot guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/src/mind/ai/llm/openai_wingman.cpp Outdated
Comment on lines +163 to +168
MF_DEBUG(
"OpenAiWingman::listModelsHttpGet() parsed response:" << endl
<< ">>>"
<< httpResponseJson.dump(4)
<< "<<<"
<< endl);
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

listModelsHttpGet() logs the full parsed JSON response (httpResponseJson.dump(4)). The /v1/models payload can be large, which may bloat logs and add noticeable overhead. Consider logging only a summary (e.g., model count / first N IDs) or gating the full dump behind a more verbose debug flag.

Suggested change
MF_DEBUG(
"OpenAiWingman::listModelsHttpGet() parsed response:" << endl
<< ">>>"
<< httpResponseJson.dump(4)
<< "<<<"
<< endl);
std::size_t modelsCount = 0;
if (httpResponseJson.contains("data") && httpResponseJson["data"].is_array()) {
modelsCount = httpResponseJson["data"].size();
}
MF_DEBUG(
"OpenAiWingman::listModelsHttpGet() parsed response: "
<< "data array size = " << modelsCount << endl);

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +45
AddLlmProviderDialog(const AddLlmProviderDialog&&) = delete;
AddLlmProviderDialog& operator=(const AddLlmProviderDialog&) = delete;
AddLlmProviderDialog& operator=(const AddLlmProviderDialog&&) = delete;
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AddLlmProviderDialog also declares deleted move operations as const AddLlmProviderDialog&& / operator=(const AddLlmProviderDialog&&). This isn’t the normal move signature and can be misleading; use AddLlmProviderDialog&& (or omit move deletions if not needed).

Suggested change
AddLlmProviderDialog(const AddLlmProviderDialog&&) = delete;
AddLlmProviderDialog& operator=(const AddLlmProviderDialog&) = delete;
AddLlmProviderDialog& operator=(const AddLlmProviderDialog&&) = delete;
AddLlmProviderDialog(AddLlmProviderDialog&&) = delete;
AddLlmProviderDialog& operator=(const AddLlmProviderDialog&) = delete;
AddLlmProviderDialog& operator=(AddLlmProviderDialog&&) = delete;

Copilot uses AI. Check for mistakes.
Comment thread lib/src/config/configuration.h Outdated
Comment on lines +1339 to +1361
string errorMessage;
bool success = false;

if (provider->providerType == WINGMAN_PROVIDER_OPENAI) {
success = config.probeOpenAiProvider(
provider->apiKey, provider->llmModel, errorMessage);
} else if (provider->providerType == WINGMAN_PROVIDER_OLLAMA) {
success = config.probeOllamaProvider(
provider->url, provider->llmModel, errorMessage);
}

if (success) {
QMessageBox::information(
this,
tr("Connection Test"),
tr("Provider configuration is valid."));
} else {
QMessageBox::critical(
this,
tr("Connection Test"),
tr("Provider configuration test failed: %1")
.arg(QString::fromStdString(errorMessage)));
}
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleTestConnection() reports success/failure but never updates provider->isValid, so the status in the “Selected Provider Details” panel won’t reflect the latest test result. Consider setting provider->isValid = success (and persisting via updateLlmProvider if needed) and then refreshing the UI.

Copilot uses AI. Check for mistakes.
Comment thread app/src/qt/dialogs/openai_config_dialog.cpp Outdated
Comment thread .github/copilot-instructions.md Outdated
Comment on lines +203 to +219
// generate unique ID using timestamp
auto now = chrono::system_clock::now();
auto timestamp = chrono::duration_cast<chrono::seconds>(now.time_since_epoch()).count();

// extract host from URL for display name
string host = url;
size_t pos = url.find("://");
if (pos != string::npos) {
host = url.substr(pos + 3);
}

providerConfig.id = "ollama-" + to_string(timestamp);
providerConfig.displayName = "ollama " + model + " @ " + host;
providerConfig.providerType = WINGMAN_PROVIDER_OLLAMA;
providerConfig.url = url;
providerConfig.llmModel = model;
providerConfig.isValid = configValid;
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provider IDs are generated using seconds-resolution timestamps (e.g., ollama-<seconds>), which can collide if the user adds providers quickly. Since IDs are used as stable keys for update/remove/active selection, consider generating IDs with higher entropy (ms + random/UUID) or checking for collisions and regenerating.

Copilot uses AI. Check for mistakes.
return getLlmProviderById(activeLlmProviderId);
}

void Configuration::addLlmProvider(const LlmProviderConfig& provider) {
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addLlmProvider() blindly appends the provider without enforcing unique IDs (despite id being documented as unique). This can create duplicate IDs (e.g., two providers added within the same second using timestamp IDs) and then getLlmProviderById()/setActiveLlmProvider() become ambiguous. Consider rejecting duplicates (and surfacing an error) or auto-renaming/regenerating the ID on collision.

Suggested change
void Configuration::addLlmProvider(const LlmProviderConfig& provider) {
void Configuration::addLlmProvider(const LlmProviderConfig& provider) {
// Enforce unique provider IDs to avoid ambiguous lookups.
if (!provider.id.empty() && getLlmProviderById(provider.id) != nullptr) {
MF_DEBUG("Configuration::addLlmProvider() duplicate ID, provider not added: " << provider.id << endl);
return;
}

Copilot uses AI. Check for mistakes.
MF_DEBUG("Configuration::removeLlmProvider() removed: " << id << endl);
}

void Configuration::setActiveLlmProvider(const string& id) {
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setActiveLlmProvider() sets activeLlmProviderId without verifying the ID exists in llmProviders. This can leave the config pointing at a non-existent provider (e.g., after update/remove sequences or malformed persisted data). Consider validating the ID (no-op with error/log, or clear active selection) to keep getActiveLlmProvider() consistent.

Suggested change
void Configuration::setActiveLlmProvider(const string& id) {
void Configuration::setActiveLlmProvider(const string& id) {
// Only set active provider if the ID is known; keep configuration consistent.
if (!id.empty() && getLlmProviderById(id) == nullptr) {
MF_DEBUG("Configuration::setActiveLlmProvider() attempted to set unknown provider id: " << id << endl);
return;
}

Copilot uses AI. Check for mistakes.
@dvorka dvorka changed the title feat(app): adding Preferences/Wingman for OpenAI and ollama (vibe coded) feat(app): ✨ adding Preferences/Wingman for OpenAI and ollama (vibe coded) May 4, 2026
dvorka and others added 5 commits May 4, 2026 17:53
- Remove legacy per-provider config fields (wingmanOpenAiApiKey, etc.)
  and replace with std::vector<LlmProviderConfig> + activeLlmProviderId
- OpenAiWingman/OpenRouterWingman now take explicit apiKey in constructor
- Configuration dialog consolidated from 4 Wingman tabs to 1 WingmanTab
- Config serializer/parser updated with backward-compatible migration
- mind.cpp initWingman()/getWingman() updated to use new provider API
- openai/ollama/openrouter config dialogs fixed to use new API

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 28 changed files in this pull request and generated 17 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

} else {
// check whether possible
if(config.canWingmanOllama()) {
if(!config.getLlmProviders().empty()) {
Comment on lines +205 to +208
// generate unique ID using timestamp
auto now = chrono::system_clock::now();
auto timestamp = chrono::duration_cast<chrono::seconds>(now.time_since_epoch()).count();
providerConfig.id = "openai-" + to_string(timestamp);
Comment on lines +197 to +208
// generate unique ID using timestamp
auto now = chrono::system_clock::now();
auto timestamp = chrono::duration_cast<chrono::seconds>(now.time_since_epoch()).count();

// extract host from URL for display name
string host = url;
size_t pos = url.find("://");
if (pos != string::npos) {
host = url.substr(pos + 3);
}

providerConfig.id = "ollama-" + to_string(timestamp);
Comment on lines +165 to +168
// generate unique ID using timestamp
auto now = chrono::system_clock::now();
auto timestamp = chrono::duration_cast<chrono::seconds>(now.time_since_epoch()).count();
providerConfig.id = "openrouter-" + to_string(timestamp);
Comment thread lib/src/representations/markdown/markdown_configuration_representation.cpp Outdated
string effectiveKey = apiKey.empty() ? std::string(envKey) : apiKey;
try {
OpenAiWingman wingman{effectiveKey};
vector<string>& models = wingman.listModels();
Comment on lines +319 to +404
// parse JSon response (OpenAI-compatible format)
nlohmann::json httpResponseJSon;
try {
httpResponseJSon = nlohmann::json::parse(command.httpResponse);
} catch (...) {
MF_DEBUG(
"Error: unable to parse OpenRouter JSon response:" << endl <<
"'" << command.httpResponse << "'" << endl
);

command.status = WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR;
command.errorMessage = "Error: unable to parse OpenRouter JSon response: '" + command.httpResponse + "'";
command.answerMarkdown.clear();
command.answerTokens = 0;
command.answerLlmModel = llmModel;

return;
}

MF_DEBUG(
"OpenRouterWingman::curlGet() parsed response:" << endl
<< ">>>"
<< httpResponseJSon.dump(4)
<< "<<<"
<< endl);

MF_DEBUG("OpenRouterWingman::curlGet() fields:" << endl);
if(httpResponseJSon.contains("model")) {
httpResponseJSon["model"].get_to(command.answerLlmModel);
MF_DEBUG(" model: " << command.answerLlmModel << endl);
}
if(httpResponseJSon.contains("usage")) {
if(httpResponseJSon["usage"].contains("prompt_tokens")) {
httpResponseJSon["usage"]["prompt_tokens"].get_to(command.promptTokens);
MF_DEBUG(" prompt_tokens: " << command.promptTokens << endl);
}
if(httpResponseJSon["usage"].contains("completion_tokens")) {
httpResponseJSon["usage"]["completion_tokens"].get_to(command.answerTokens);
MF_DEBUG(" answer_tokens: " << command.answerTokens << endl);
}
}
if(httpResponseJSon.contains("choices")
&& httpResponseJSon["choices"].size() > 0
) {
auto choice = httpResponseJSon["choices"][0];
if(choice.contains("message")
&& choice["message"].contains("content")
) {
choice["message"]["content"].get_to(command.answerMarkdown);
m8r::replaceAll(
"\n",
"<br/>",
command.answerMarkdown);
MF_DEBUG(" answer (HTML): " << command.answerMarkdown << endl);
}
if(choice.contains("finish_reason")) {
string statusStr{};
choice["finish_reason"].get_to(statusStr);
if(statusStr == "stop") {
command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_OK;
} else {
command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR;
command.errorMessage.assign(
"OpenRouter API HTTP request failed with finish_reason: "
+ statusStr);
command.answerMarkdown.clear();
command.answerTokens = 0;
command.answerLlmModel = llmModel;
}
MF_DEBUG(" status: " << command.status << endl);
}
} else {
command.status = m8r::WingmanStatusCode::WINGMAN_STATUS_CODE_ERROR;
command.answerMarkdown.clear();
command.answerTokens = 0;
command.answerLlmModel = llmModel;
if(
httpResponseJSon.contains("error")
&& httpResponseJSon["error"].contains("message")
) {
httpResponseJSon["error"]["message"].get_to(command.errorMessage);
} else {
command.errorMessage.assign(
"No choices in the OpenRouter API HTTP response");
}
}
Comment on lines +50 to +51
"OpenRouterWingman::OpenRouterWingman() apiKey: '"
<< this->apiKey << "'" << endl);
Comment on lines 54 to +56
MF_DEBUG(
"OpenAiWingman::OpenAiWingman() apiKey: '"
<< config.getWingmanOpenAiApiKey() << "'" << endl);
<< this->apiKey << "'" << endl);
QMessageBox::information(
this,
tr("Models Refreshed"),
tr("Successfully fetched %1 models from OpenAI API.").arg(models.size()));
@dvorka dvorka added the vibe label May 4, 2026
@dvorka dvorka changed the title feat(app): ✨ adding Preferences/Wingman for OpenAI and ollama (vibe coded) feat(app): ✨ adding Preferences/Wingman for ollama, OpenRouter and OpenAI May 4, 2026
dvorka and others added 4 commits May 4, 2026 21:16
…r Qt5+WebEngine

macos-latest now maps to macOS 15 on Apple Silicon (ARM64) where
Homebrew's qt@5 no longer includes QtWebEngine. Qt5 official builds
with WebEngine only exist for x86_64, so switch to:
- macos-13 (Intel x86_64) runner
- jurplel/install-qt-action@v4 for Qt 5.15.2 with qtwebengine module
- keep ccache via brew, remove the now-redundant qt@5 brew install

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
moc processes #ifdef guards with its own limited preprocessor pass.
When DO_MF_DEBUG is defined, moc includes doActionMindHack() in the
meta-object table unconditionally, but the #ifdef in the .cpp caused
the function body to be absent in non-debug builds, yielding:
  undefined reference to MainWindowPresenter::doActionMindHack()

Fix: always declare and define the slot; keep debug logic inside the
body under #ifdef DO_MF_DEBUG so both moc and the linker are satisfied
regardless of build configuration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two fixes:

1. doActionMindHack - restore proper #ifdef DO_MF_DEBUG guards around
   the slot declaration and implementation so the entire hook is
   compiled out in production builds (build-rc). All related code in
   main_menu_view.h/.cpp and main_menu_presenter.cpp was already
   correctly guarded; the slot itself is now also fully excluded.

2. actionMindSemanticSearch - remove incorrect #ifdef DO_MF_DEBUG
   guard from its field declaration in main_menu_view.h. This is a
   real production feature (Wingman semantic search), not a debug
   hook; the guard caused a build failure when DO_MF_DEBUG is not
   defined (e.g. build-rc).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 30 out of 31 changed files in this pull request and generated 10 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

} else {
// check whether possible
if(config.canWingmanOllama()) {
if(!config.getLlmProviders().empty()) {
Comment on lines +861 to +863
if (configDialog.exec() == QDialog::Accepted) {
config.addLlmProvider(configDialog.getProviderConfig());
refresh();

return false;
bool Configuration::isWingman() {
return !llmProviders.empty() && !activeLlmProviderId.empty();
Comment on lines +391 to +392
bool Configuration::isWingman() {
return !llmProviders.empty() && !activeLlmProviderId.empty();
Comment on lines +470 to +472
// TODO: actually test the connection by calling OpenAI API
// For now, just validate the inputs
MF_DEBUG("Configuration::probeOpenAiProvider() validated: " << model << endl);
Comment on lines +492 to +494
// TODO: actually test the connection by calling ollama API
// For now, just validate the inputs
MF_DEBUG("Configuration::probeOllamaProvider() validated: " << url << ", " << model << endl);
*/
bool Configuration::isWingman() {
return WingmanLlmProviders::WINGMAN_PROVIDER_NONE==wingmanProvider?false:true;
MF_DEBUG("Configuration::probeOpenRouterProvider() validated: " << model << endl);
Comment on lines +50 to +51
"OpenRouterWingman::OpenRouterWingman() apiKey: '"
<< this->apiKey << "'" << endl);
Comment thread app/src/qt/dialogs/configuration_dialog.cpp Outdated
Comment thread lib/src/representations/markdown/markdown_configuration_representation.cpp Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 40 out of 41 changed files in this pull request and generated 14 comments.

Comment on lines 2236 to 2238
string answerDescriptor{
"[provider: " + config.getWingmanLlmProviderAsString(config.getWingmanLlmProvider()) +
"[provider: " + config.getLlmProviderById(config.getActiveLlmProviderId())->displayName +
", model: " + commandWingmanChat.answerLlmModel +

return false;
bool Configuration::isWingman() {
return !llmProviders.empty() && !activeLlmProviderId.empty();
Comment thread lib/src/mind/mind.cpp
Comment on lines +1493 to +1502
string effectiveKey = provider->apiKey;
if(provider->useEnvVar) {
const char* envKey = std::getenv(ENV_VAR_OPENAI_API_KEY);
if(envKey) {
effectiveKey = string(envKey);
MF_DEBUG(" MIND Wingman OpenAI: using env var key" << endl);
}
}
wingman = (Wingman*)new OpenAiWingman{effectiveKey};
wingman->setLlmModel(provider->llmModel);
Comment thread lib/src/mind/mind.cpp
Comment on lines +1513 to +1522
string effectiveKey = provider->apiKey;
if(provider->useEnvVar) {
const char* envKey = std::getenv(ENV_VAR_OPENROUTER_API_KEY);
if(envKey) {
effectiveKey = string(envKey);
MF_DEBUG(" MIND Wingman OpenRouter: using env var key" << endl);
}
}
wingman = (Wingman*)new OpenRouterWingman{effectiveKey};
wingman->setLlmModel(provider->llmModel);
Comment on lines +72 to +78
/**
* @brief Get embeddings (not supported by OpenRouter Wingman).
*/
virtual void embeddings(CommandWingmanEmbeddings& command) override {
UNUSED_ARG(command);
throw std::runtime_error("OpenRouter Wingman does not support embeddings");
}
Comment on lines +942 to +964
string errorMessage;
bool success = false;

if (provider->providerType == WINGMAN_PROVIDER_OPENAI) {
success = config.probeOpenAiProvider(
provider->apiKey, provider->llmModel, errorMessage);
} else if (provider->providerType == WINGMAN_PROVIDER_OLLAMA) {
success = config.probeOllamaProvider(
provider->url, provider->llmModel, errorMessage);
} else if (provider->providerType == WINGMAN_PROVIDER_OPENROUTER) {
success = config.probeOpenRouterProvider(
provider->apiKey, provider->llmModel, errorMessage);
}

/*
* Navigator tab
*/
if (success) {
provider->isValid = true;
statusValue->setText(tr("configured"));
statusValue->setStyleSheet("QLabel { color: green; }");
QMessageBox::information(
this,
tr("Connection Test"),
tr("Provider configuration is valid."));
} else {
Comment on lines +616 to +624
s << CONFIG_SETTING_MIND_WINGMAN_PROVIDER_ITEM
<< p.id << "|"
<< p.displayName << "|"
<< Configuration::getWingmanLlmProviderAsString(p.providerType) << "|"
<< p.url << "|"
<< p.apiKey << "|"
<< p.llmModel << "|"
<< (p.isValid ? "1" : "0") << "|"
<< (p.useEnvVar ? "1" : "0") << endl;
Comment on lines +418 to +419
// format: id|displayName|typeStr|url|apiKey|llmModel|isValid
std::vector<std::string> parts;
Comment on lines +197 to +212
/**
* @brief LLM Provider Configuration
*
* Represents configuration for a single Large Language Model provider.
* Supports OpenAI and ollama providers with provider-specific fields.
*/
struct LlmProviderConfig {
std::string id; // unique identifier (e.g., "openai-1", "ollama-local")
std::string displayName; // user-friendly name (e.g., "OpenAI GPT-4", "Local Ollama")
WingmanLlmProviders providerType; // WINGMAN_PROVIDER_OPENAI, WINGMAN_PROVIDER_OLLAMA
std::string url; // for ollama: base URL, for OpenAI: empty
std::string apiKey; // for OpenAI/OpenRouter: stored API key (empty when useEnvVar is true)
std::string llmModel; // model name (e.g., "gpt-4", "llama2")
bool isValid; // whether configuration was validated/probed
bool useEnvVar; // if true, resolve API key from environment variable at runtime

Comment on lines +413 to +446
} else if(line->find(CONFIG_SETTING_MIND_ACTIVE_WINGMAN_PROVIDER) != std::string::npos) {
string id = line->substr(strlen(CONFIG_SETTING_MIND_ACTIVE_WINGMAN_PROVIDER));
c.setActiveLlmProvider(id);
} else if(line->find(CONFIG_SETTING_MIND_WINGMAN_PROVIDER_ITEM) != std::string::npos) {
string v = line->substr(strlen(CONFIG_SETTING_MIND_WINGMAN_PROVIDER_ITEM));
// format: id|displayName|typeStr|url|apiKey|llmModel|isValid
std::vector<std::string> parts;
std::stringstream ss(v);
std::string part;
while(std::getline(ss, part, '|')) {
parts.push_back(part);
}
if(parts.size() >= 7) {
LlmProviderConfig p;
p.id = parts[0];
p.displayName = parts[1];
p.url = parts[3];
p.apiKey = parts[4];
p.llmModel = parts[5];
p.isValid = (parts[6] == "1");
p.useEnvVar = (parts.size() >= 8 && parts[7] == "1");
string typeStr = parts[2];
if(typeStr == Configuration::getWingmanLlmProviderAsString(WingmanLlmProviders::WINGMAN_PROVIDER_OPENAI)) {
p.providerType = WingmanLlmProviders::WINGMAN_PROVIDER_OPENAI;
} else if(typeStr == Configuration::getWingmanLlmProviderAsString(WingmanLlmProviders::WINGMAN_PROVIDER_OLLAMA)) {
p.providerType = WingmanLlmProviders::WINGMAN_PROVIDER_OLLAMA;
} else if(typeStr == Configuration::getWingmanLlmProviderAsString(WingmanLlmProviders::WINGMAN_PROVIDER_OPENROUTER)) {
p.providerType = WingmanLlmProviders::WINGMAN_PROVIDER_OPENROUTER;
} else {
p.providerType = WingmanLlmProviders::WINGMAN_PROVIDER_NONE;
}
c.addLlmProvider(p);
}
}
@dvorka dvorka linked an issue May 8, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] OpenRouter [Feature] note-taking w/ local LLM @ ollama [Enhancement] Add ability to choose LLM model

2 participants