Skip to content

feat(ai): wire Config.ai.extraModels for user-defined OpenAI-compat providers#176

Open
ImIvanGil wants to merge 1 commit into
Axenide:mainfrom
ImIvanGil:feat/ai-custom-provider-openai-compat
Open

feat(ai): wire Config.ai.extraModels for user-defined OpenAI-compat providers#176
ImIvanGil wants to merge 1 commit into
Axenide:mainfrom
ImIvanGil:feat/ai-custom-provider-openai-compat

Conversation

@ImIvanGil
Copy link
Copy Markdown

Context

Config.ai.extraModels is declared in the schema:

  • config/defaults/ai.js:4"extraModels": []
  • config/Config.qml:1180property list<var> extraModels: []

But it's never consumed anywhere in the codebase (verified with grep -rn extraModels). As a result, users have no way to register custom OpenAI-compatible providers — Moonshot/Kimi, OpenRouter, LMStudio remote, llama.cpp HTTP servers, free-tier providers, etc. — beyond the hardcoded list (Gemini/OpenAI/Mistral/Groq/Anthropic/Ollama/MiniMax).

The AiPanel.qml UI has a "Custom Provider" section with fields for API Key + Endpoint + cURL Template, but only the API Key Save button is wired — the Endpoint and cURL Template fields try to persist to Config.ai.customEndpoint and Config.ai.customCurlTemplate, neither of which exist in the schema. So the full "Custom Provider" flow is broken upstream.

What this PR does

Wires Config.ai.extraModels end-to-end so users can configure arbitrary OpenAI-compat providers without UI changes (UI changes can be a follow-up). fetchAvailableModels() now iterates the array and registers each entry as a regular AiModel.

The "custom" provider already routes to OpenAiApiStrategy via getStrategyForProvider() (Ai.qml:117), so the request/parse path is already correct.

Entry schema (~/.config/ambxst/config/ai.json)

{
    "extraModels": [
        {
            "name":         "Kimi K2 (0905)",
            "model":        "kimi-k2-0905-preview",
            "endpoint":     "https://api.moonshot.ai/v1",
            "provider":     "custom",
            "description":  "Moonshot K2 preview",
            "requires_key": true,
            "key_provider": "custom"
        },
        {
            "name":         "Local Llama 3",
            "model":        "llama-3-70b",
            "endpoint":     "http://localhost:8080/v1",
            "provider":     "custom",
            "requires_key": false
        }
    ]
}
  • provider: "custom" → uses OpenAiApiStrategy (default for unknown providers anyway).
  • requires_key: true (default) → looks up API key from KeyStore under key_provider (default "custom"). The existing "Custom Provider API Key" Save button in AiPanel.qml populates that KeyStore entry — so the end-to-end flow works without UI changes.
  • requires_key: false → for local/no-auth endpoints (Ollama remote, llama.cpp without keys, etc.).

Why this approach

  • Uses an existing schema field — no Config.qml changes, no schema migration.
  • No UI changes required — the existing API Key Save button is enough; users edit ai.json for the rest, same pattern as systemPrompt.
  • Future UI can be a follow-up PRAiPanel.qml's broken "Custom Provider" Endpoint/cURL fields could be reworked to manage extraModels entries directly, but that's a UX call best made separately.
  • Generic — works for any OpenAI-compat provider, not just Moonshot. Adding the next provider doesn't require new code.

Tested with

  • Moonshot / Kimi: registered kimi-k2-0905-preview, kimi-k2.6, kimi-k2-thinking, moonshot-v1-{8k,32k,128k} — all appear in dropdown, requests fire correctly, responses parse via OpenAiApiStrategy.
  • Verified key flow: storing sk-... via the existing "Custom Provider API Key" Save button + adding entries to ai.json produced working models with zero panel-level glitches.

Diff stats

modules/services/Ai.qml | 58 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 58 insertions(+)

Related PRs

This is the third in a series of Ambxst fixes. Related:

A follow-up PR will add reasoning_content + dynamic-temperature support so thinking models (Kimi K2.5/K2.6, GPT-o1, Claude thinking variants) work via the same flow.

…roviders

`Config.ai.extraModels` was declared in the schema (config/defaults/ai.js
and config/Config.qml line 1180) but never consumed by Ai.qml. As a
result, users had no way to register custom OpenAI-compatible providers
— Moonshot/Kimi, OpenRouter, LMStudio remote, llama.cpp HTTP servers,
groqcloud variants, etc. — beyond the hardcoded provider list.

This wires the existing schema field: `fetchAvailableModels()` now
iterates `Config.ai.extraModels` and registers each entry as a regular
AiModel. The "custom" provider already routes to OpenAiApiStrategy via
`getStrategyForProvider`, so the request/parse path is already correct
for any OpenAI-compat endpoint.

Entry schema (in ~/.config/ambxst/config/ai.json):

    "extraModels": [
        {
            "name":         "Kimi K2 (0905)",
            "model":        "kimi-k2-0905-preview",
            "endpoint":     "https://api.moonshot.ai/v1",
            "provider":     "custom",
            "description":  "Moonshot K2 preview",
            "requires_key": true,
            "key_provider": "custom"
        }
    ]

The API key is read from KeyStore under `key_provider` (default "custom"),
which the existing "Custom Provider API Key" Save button in AiPanel.qml
already populates correctly. So the end-to-end flow now works without
any UI changes.

A future PR could add UI to manage the extraModels array from the panel
directly. For now, users edit ai.json — same pattern as systemPrompt
and other config fields.

Tested with Moonshot (Kimi K2 family + moonshot-v1-{8k,32k,128k}). Each
custom model appears in the dropdown and gets requested correctly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant