✨ Register local LLM providers (Ollama, llama.cpp, LocalAI, vLLM, LM Studio, RHAIIS) in agent selector#8248
Conversation
Adds Ollama, llama.cpp, LocalAI, vLLM, LM Studio and Red Hat AI Inference Server as CapabilityChat-only providers in the agent registry, and also flips on the existing Groq / OpenRouter / Open WebUI providers that had been sitting unregistered in the tree. Backend ------- - New pkg/agent/provider_local_openai_compat.go defines a generic LocalOpenAICompatProvider parameterized by (name, displayName, urlEnvVar, defaultURL, chatPath) and instantiates one factory per runner. Each provider reads its own URL env var (OLLAMA_URL, LLAMACPP_URL, LOCALAI_URL, VLLM_URL, LM_STUDIO_URL, RHAIIS_URL) with loopback defaults only for the workstation-first runners (Ollama at 127.0.0.1:11434, LM Studio at 127.0.0.1:1234). IsAvailable() returns true only when a URL is resolvable, so the dropdown honestly reflects which runners the operator has configured. - A sentinel placeholder API key is seeded for unauthenticated local runners so the shared OpenAI-compatible helper's Authorization header is well formed. Real keys from env or ~/.kc/config.yaml always win. - Registration lives after the tool-capable CLI agents in InitializeProviders so promoteExecutingDefault() still picks a mission-capable agent as the default. The architectural comment at registry.go:303 is expanded to explain the chat-vs-missions split and why vendor API agents remain unregistered. - config.go gains env-var mappings for each new provider key so the ConfigManager honors GetAPIKey / GetModel for them. Frontend -------- - web/src/types/agent.ts adds ollama, llamacpp, localai, vllm, lm-studio and rhaiis to the AgentProvider union. - AgentIcon.tsx adds icon cases for each new provider. - agentSelectorUtils.ts adds LOCAL_LLM_INSTALL_MISSIONS mapping provider name -> install mission ID, and enriches unavailable local LLM agents with installMissionId so the dropdown surfaces the install mission path when the runner is not yet reachable. Matches the kagent/kagenti pattern that already ships install links. Validation ---------- - go build ./... passes. - go test ./pkg/agent/... passes (4.2s). - npm run build passes all post-build safety checks. - Agent selector tests (17 tests) still pass. Signed-off-by: Andrew Anderson <[email protected]>
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
✅ Deploy Preview for kubestellarconsole ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
👋 Hey @clubanderson — thanks for opening this PR!
This is an automated message. |
Updates SECURITY-MODEL.md §3 to reflect #8248, which registers Ollama, llama.cpp, LocalAI, vLLM, LM Studio, RHAIIS, Groq, OpenRouter and Open WebUI as chat-only agent providers in InitializeProviders. Changes: - Provider table flips the Registered column from "no" to "yes (chat only)" for the nine HTTP providers that are now wired into the agent dropdown, and adds rows for the six new local LLM runners with their env vars and default URLs. - Explains the chat-only capability flag and why missions still route through the tool-capable CLI agents (registry.go:303 rationale). - Adds a "Local LLM strategy" subsection that cross-links the docs.kubestellar.io local-llm-strategy page and the eight install missions on kubestellar/console-kb. - Replaces the "Planned follow-up" subsection with active recipes for each runner — Ollama loopback default, in-cluster Service URLs for llama.cpp/LocalAI/vLLM/RHAIIS, LM Studio workstation default, and Groq/OpenRouter/Open WebUI gateway overrides. The "# PLANNED — not yet wired at runtime" bash comments are removed. The threat model claims about kubeconfig and credentials staying out of the request body are unchanged and still authoritative. Signed-off-by: Andrew Anderson <[email protected]>
There was a problem hiding this comment.
Pull request overview
Adds support for selecting local OpenAI-compatible LLM runners (plus enabling previously-unregistered chat-only providers) in the agent registry and Console UI, while keeping mission execution routed to tool-capable CLI agents.
Changes:
- Backend: Introduces
LocalOpenAICompatProviderand registers Ollama/llama.cpp/LocalAI/vLLM/LM Studio/RHAIIS, plus registers Groq/OpenRouter/Open WebUI. - Backend: Adds env-var mappings for API key + model configuration for the new providers.
- Frontend: Extends agent provider types, adds icons, and enriches unavailable agents with install-mission IDs.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| web/src/types/agent.ts | Expands AgentProvider union to include new local-runner provider keys. |
| web/src/components/agent/agentSelectorUtils.ts | Adds install-mission mapping and enriches agent entries to surface install paths in the selector. |
| web/src/components/agent/AgentIcon.tsx | Adds icon renderers for the newly-supported local providers. |
| pkg/agent/registry.go | Registers new chat-only local providers and enables Groq/OpenRouter/Open WebUI in provider initialization. |
| pkg/agent/provider_local_openai_compat.go | New generic provider implementation for local OpenAI-compatible runners, with per-runner factories/defaults. |
| pkg/agent/config.go | Adds env-var key/model mappings for the new providers. |
| func ensureLocalLLMPlaceholderKey(providerKey string) { | ||
| cm := GetConfigManager() | ||
| if cm.GetAPIKey(providerKey) == "" { | ||
| _ = cm.SetAPIKey(providerKey, localLLMPlaceholderKey) | ||
| } | ||
| } |
There was a problem hiding this comment.
ensureLocalLLMPlaceholderKey() persists a sentinel API key by calling ConfigManager.SetAPIKey(), which writes to ~/.kc/config.yaml. This creates surprising disk side effects (and can fail on read-only filesystems) just to satisfy the OpenAI-compat helper’s non-empty Authorization requirement. Prefer avoiding disk writes here (e.g., allow the OpenAI-compat helper to omit Authorization when apiKey is empty for specific providers, or pass an explicit placeholder header without mutating persisted config); at minimum, handle/log the SetAPIKey error and return a clearer failure instead of silently ignoring it.
| // IsAvailable returns true when either the env var is set or the provider has | ||
| // a non-empty default URL. Local runners typically have no API key — the OpenAI | ||
| // helper accepts a sentinel value when CapabilityChat is the only capability, | ||
| // so availability is driven by URL reachability rather than credential state. | ||
| func (p *LocalOpenAICompatProvider) IsAvailable() bool { | ||
| return p.localOpenAICompatBaseURL() != "" | ||
| } |
There was a problem hiding this comment.
IsAvailable() currently returns true whenever localOpenAICompatBaseURL() is non-empty. Because Ollama and LM Studio have non-empty defaultURL values, they will report Available=true even when the runner is not actually running, which affects the agent-selector “available” signal and also makes Registry.HasAvailableProviders() true on fresh installs. Consider either (1) making availability depend on an explicit env var opt-in for these runners, or (2) adding a lightweight reachability probe with a short timeout (and returning false when unreachable) so “available” reflects reality.
| export const LOCAL_LLM_INSTALL_MISSIONS: Readonly<Record<string, string>> = Object.freeze({ | ||
| ollama: 'install-ollama', | ||
| llamacpp: 'install-llama-cpp', | ||
| localai: 'install-localai', | ||
| vllm: 'install-vllm', | ||
| 'lm-studio': 'install-lm-studio', | ||
| rhaiis: 'install-rhaiis', | ||
| 'open-webui': 'install-open-webui', | ||
| 'claude-desktop': 'install-claude-desktop', | ||
| }) |
There was a problem hiding this comment.
LOCAL_LLM_INSTALL_MISSIONS is typed as Record<string, string>, so indexing with an unknown agent name is typed as always returning a string even though at runtime it can be undefined. This defeats type-safety for the enrichment logic. Use a Partial<Record<string, string>> (or Record<string, string | undefined>) so missionId is correctly typed as possibly undefined.
| // leaving the Console. Keep this map in sync with the provider keys in | ||
| // pkg/agent/provider_local_openai_compat.go. |
There was a problem hiding this comment.
The comment says to keep this map in sync with provider keys in pkg/agent/provider_local_openai_compat.go, but the map also includes entries for open-webui and claude-desktop which are defined/registered elsewhere. Please update the comment to reflect what actually needs to stay in sync (e.g., include other chat-only providers) to avoid future confusion.
| // leaving the Console. Keep this map in sync with the provider keys in | |
| // pkg/agent/provider_local_openai_compat.go. | |
| // leaving the Console. Keep this map in sync with the backend-registered | |
| // local/chat-only provider keys that should expose install missions here, | |
| // including the providers in pkg/agent/provider_local_openai_compat.go and | |
| // other chat-only providers such as open-webui and claude-desktop. |
…+ list local LLM providers in API keys settings Two small follow-ups on the local-LLM registration work in the preceding commit: 1. Groq and OpenRouter key validation were hard-coded to hit the public hostnames (api.groq.com, openrouter.ai) even when GROQ_BASE_URL or OPENROUTER_BASE_URL redirected the runtime chat path at a local runner. That produced a false negative on the settings page for operators pointing Groq at a local Ollama or an internal corporate gateway. The validator now resolves the validation URL from the same base URL resolution helper the Chat method uses, so self-hosted and enterprise gateways validate correctly. 2. The APIKeySettings modal's PROVIDER_INFO map and providerToIconMap now include entries for the six local LLM runners (ollama, llamacpp, localai, vllm, lm-studio, rhaiis). The placeholder copy tells the operator which URL env var to set, since local runners typically run unauthenticated. A dedicated Base URL input field per provider is tracked as a follow-up issue. Signed-off-by: Andrew Anderson <[email protected]>
…utes (#8252) * 📝 docs(security): mark local LLM providers registered + active Updates SECURITY-MODEL.md §3 to reflect #8248, which registers Ollama, llama.cpp, LocalAI, vLLM, LM Studio, RHAIIS, Groq, OpenRouter and Open WebUI as chat-only agent providers in InitializeProviders. Changes: - Provider table flips the Registered column from "no" to "yes (chat only)" for the nine HTTP providers that are now wired into the agent dropdown, and adds rows for the six new local LLM runners with their env vars and default URLs. - Explains the chat-only capability flag and why missions still route through the tool-capable CLI agents (registry.go:303 rationale). - Adds a "Local LLM strategy" subsection that cross-links the docs.kubestellar.io local-llm-strategy page and the eight install missions on kubestellar/console-kb. - Replaces the "Planned follow-up" subsection with active recipes for each runner — Ollama loopback default, in-cluster Service URLs for llama.cpp/LocalAI/vLLM/RHAIIS, LM Studio workstation default, and Groq/OpenRouter/Open WebUI gateway overrides. The "# PLANNED — not yet wired at runtime" bash comments are removed. The threat model claims about kubeconfig and credentials staying out of the request body are unchanged and still authoritative. Signed-off-by: Andrew Anderson <[email protected]> * 🔥 chore(ci): delete 10 dead Copilot workflows + reclaim wasted CI minutes Third of four PRs from the fullsend-ai/fullsend automation evaluation. The plan was to replace copilot-pr-monitor.yml's minutely cron poll with webhook triggers — but closer inspection found 10 workflows in the same fully-disabled state, and two of them were burning CI minutes on unconditional cron schedules: - copilot-pr-monitor.yml: cron * * * * * → 1440 wasted runs/day - copilot-retry.yml: cron */5 * * * * → 288 wasted runs/day Every job in every deleted workflow is guarded by: if: false # Copilot disabled — issues handled by Claude Code scanner Deleting them rather than refactoring is the right call per fullsend's "repo-as-coordinator via native primitives" principle — dead code pretending to orchestrate nothing is worse than no code at all. Deleted: - copilot-pr-monitor.yml (minutely cron poll, 100% no-op) - copilot-retry.yml (5-min cron, 100% no-op) - copilot-assigned.yml - copilot-automation.yml - copilot-build-check.yml - copilot-build-monitor.yml - copilot-dco.yml - copilot-recovery.yml - copilot-review-apply.yml - ai-fix.yml Kept: - copilot-comment-followup.yml — active, reads Copilot review comments on merged PRs. Unrelated to the deleted pipeline. - copilot-setup-steps.yml — harmless workflow_dispatch-only helper. Verification: - No `workflow_run:` trigger in workflow-failure-issue.yml references any of the deleted workflow names. - No `uses: ./.github/workflows/...` references to any of the deleted files. - The `ai-fix-requested` label name is unrelated to `ai-fix.yml` — label stays, workflow file goes. Not a replacement for the Claude Code scanner approach — that's already running and owns the issue-fix loop now. This PR is pure cleanup. Savings: ~1728 dead workflow runs per day, plus a lot less noise in the Actions tab. Signed-off-by: Andrew Anderson <[email protected]> --------- Signed-off-by: Andrew Anderson <[email protected]>
…red output (#8253) * 📝 docs(security): mark local LLM providers registered + active Updates SECURITY-MODEL.md §3 to reflect #8248, which registers Ollama, llama.cpp, LocalAI, vLLM, LM Studio, RHAIIS, Groq, OpenRouter and Open WebUI as chat-only agent providers in InitializeProviders. Changes: - Provider table flips the Registered column from "no" to "yes (chat only)" for the nine HTTP providers that are now wired into the agent dropdown, and adds rows for the six new local LLM runners with their env vars and default URLs. - Explains the chat-only capability flag and why missions still route through the tool-capable CLI agents (registry.go:303 rationale). - Adds a "Local LLM strategy" subsection that cross-links the docs.kubestellar.io local-llm-strategy page and the eight install missions on kubestellar/console-kb. - Replaces the "Planned follow-up" subsection with active recipes for each runner — Ollama loopback default, in-cluster Service URLs for llama.cpp/LocalAI/vLLM/RHAIIS, LM Studio workstation default, and Groq/OpenRouter/Open WebUI gateway overrides. The "# PLANNED — not yet wired at runtime" bash comments are removed. The threat model claims about kubeconfig and credentials staying out of the request body are unchanged and still authoritative. Signed-off-by: Andrew Anderson <[email protected]> * ✨ feat(ci): decomposed-review prompt for Claude Code review — structured output Fourth of four PRs from the fullsend-ai/fullsend automation evaluation. Adopts fullsend's "decomposed code review" pattern (docs/problems/code-review.md) — split review into specialized concerns (correctness, security, style) rather than one monolithic pass. Done as a single LLM call with structured output, not 3 parallel jobs, to keep token cost neutral. The prompt asks the existing /code-review:code-review plugin to organize its findings into three explicit sections with P0/P1/P2 priority tags, and to write "None." under any section that has nothing to report so it doesn't fabricate issues. The SECURITY section references docs/security/SECURITY-AI.md (added in PR #8249) so the reviewer explicitly watches for the six threat categories — external prompt injection, insider credentials, DoS, agent drift, supply chain, agent-to-agent injection — on any PR that touches LLM-calling code. Only change is the `prompt:` field in the existing `.github/workflows/claude-code-review.yml`. No new actions, no new secrets, no cost increase. Expected behavior after merge: - Every PR's Claude Code review comment now has a CORRECTNESS / SECURITY / STYLE structure instead of prose. - Every issue is tagged P0/P1/P2 so reviewers can triage quickly. - A pure doc PR should show "None." in all three sections, not fabricated nits. - A PR touching LLM-calling code should produce at least one item in SECURITY referencing prompt-injection risk. Signed-off-by: Andrew Anderson <[email protected]> --------- Signed-off-by: Andrew Anderson <[email protected]>
Updates SECURITY-MODEL.md §3 to reflect #8248, which registers Ollama, llama.cpp, LocalAI, vLLM, LM Studio, RHAIIS, Groq, OpenRouter and Open WebUI as chat-only agent providers in InitializeProviders. Changes: - Provider table flips the Registered column from "no" to "yes (chat only)" for the nine HTTP providers that are now wired into the agent dropdown, and adds rows for the six new local LLM runners with their env vars and default URLs. - Explains the chat-only capability flag and why missions still route through the tool-capable CLI agents (registry.go:303 rationale). - Adds a "Local LLM strategy" subsection that cross-links the docs.kubestellar.io local-llm-strategy page and the eight install missions on kubestellar/console-kb. - Replaces the "Planned follow-up" subsection with active recipes for each runner — Ollama loopback default, in-cluster Service URLs for llama.cpp/LocalAI/vLLM/RHAIIS, LM Studio workstation default, and Groq/OpenRouter/Open WebUI gateway overrides. The "# PLANNED — not yet wired at runtime" bash comments are removed. The threat model claims about kubeconfig and credentials staying out of the request body are unchanged and still authoritative. Signed-off-by: Andrew Anderson <[email protected]>
|
Thank you for your contribution! Your PR has been merged. Check out what's new:
Stay connected: Slack #kubestellar-dev | Multi-Cluster Survey |
|
Post-merge build verification passed ✅ Both Go and frontend builds compiled successfully against merge commit |
✅ Post-Merge Verification: passedCommit: |
…RL Advanced section Part 2 of #8254. The Settings → API Keys modal now renders the nine chat-only providers (Groq, OpenRouter, Open WebUI + Ollama, llama.cpp, LocalAI, vLLM, LM Studio, RHAIIS) with an Advanced expandable section that accepts a per-provider Base URL override. Backend changes --------------- - KeyStatus gains BaseURL, BaseURLEnvVar, BaseURLSource fields so the frontend can render the current resolved value and indicate whether it came from env or config file. - handleGetKeysStatus now returns the nine chat-only providers (was previously an empty list per the "API-key-driven agents are hidden" comment, which predated PR #8248 flipping them on). CLI-based tool-capable agents (claude-code, bob, codex, ...) remain omitted — they manage their own credentials. - SetKeyRequest.APIKey is now optional: setting just BaseURL is the common path for unauthenticated local LLM runners. handleSetKey accepts any combination of apiKey, baseURL, model — at least one is required. Base URL is syntactically validated (http(s)://, no whitespace) via validateBaseURL before being saved. On successful save, cached key validity is invalidated so the next validation hits the new endpoint. - Key validation is skipped for local LLM runners (validationRequired is false on their providerDef entries). There is no meaningful way to validate the sentinel "local-llm-no-auth" placeholder against a real endpoint, so we report Configured=true based on URL presence. Frontend changes ---------------- - KeyStatus TS interface gains baseURL, baseURLEnvVar, baseURLSource. - APIKeySettings.tsx adds an Advanced expandable to each row. It only renders when the backend reports baseURLEnvVar (meaning the provider actually supports an override). The form includes a text input, an inline "env var wins" hint when baseURLSource is "env", syntactic validation feedback, and a post-save "restart kc-agent to apply" message. The summary row shows the current resolved value next to the Advanced toggle so operators can see it at a glance. Closes #8254. - go build ./... passes. - go test ./pkg/agent/... passes (flaky auto-update test unrelated). - npm run build passes all post-build safety checks. Signed-off-by: Andrew Anderson <[email protected]>
…closes #8254) (#8256) * ✨ feat(agent): ConfigManager.GetBaseURL/SetBaseURL + dynamic provider base URL resolution Part 1 of #8254. Adds per-provider base URL storage in ~/.kc/config.yaml and updates all nine chat-only HTTP providers to resolve their base URL dynamically at request time, so changes take effect without restarting kc-agent. Backend changes --------------- - AgentKeyConfig gains an optional `base_url` YAML field alongside api_key and model. Empty string means "use the compiled-in default". - ConfigManager.GetBaseURL(provider) / SetBaseURL / RemoveBaseURL mirror the existing GetAPIKey / SetAPIKey / RemoveAPIKey shape. The precedence chain is env var → config file → compiled default, same as GetAPIKey and GetModel. - getBaseURLEnvKeyForProvider maps each registered provider key to its URL env var (OLLAMA_URL, LLAMACPP_URL, LOCALAI_URL, VLLM_URL, LM_STUDIO_URL, RHAIIS_URL, GROQ_BASE_URL, OPENROUTER_BASE_URL, OPEN_WEBUI_URL). Provider refactor ----------------- - LocalOpenAICompatProvider.localOpenAICompatBaseURL() now walks env var → ConfigManager.GetBaseURL → struct default. All six local LLM runners use this path. - GroqProvider drops its cached baseURL field and resolves via a new groqResolveBaseURL() helper (env → config → default). The existing groqValidationURL() already consults the same helper, so key validation continues to honor the override. - OpenRouterProvider and OpenWebUIProvider get the same treatment — dynamic resolution via openRouterResolveBaseURL() and openWebUIResolveBaseURL() respectively. The frontend UI (APIKeySettings Advanced section with a Base URL input field per provider) is Part 2 of this PR, coming next. - go build ./... passes. - go test ./pkg/agent/... passes. Signed-off-by: Andrew Anderson <[email protected]> * ✨ feat(ui): Settings → API Keys lists local LLM providers with Base URL Advanced section Part 2 of #8254. The Settings → API Keys modal now renders the nine chat-only providers (Groq, OpenRouter, Open WebUI + Ollama, llama.cpp, LocalAI, vLLM, LM Studio, RHAIIS) with an Advanced expandable section that accepts a per-provider Base URL override. Backend changes --------------- - KeyStatus gains BaseURL, BaseURLEnvVar, BaseURLSource fields so the frontend can render the current resolved value and indicate whether it came from env or config file. - handleGetKeysStatus now returns the nine chat-only providers (was previously an empty list per the "API-key-driven agents are hidden" comment, which predated PR #8248 flipping them on). CLI-based tool-capable agents (claude-code, bob, codex, ...) remain omitted — they manage their own credentials. - SetKeyRequest.APIKey is now optional: setting just BaseURL is the common path for unauthenticated local LLM runners. handleSetKey accepts any combination of apiKey, baseURL, model — at least one is required. Base URL is syntactically validated (http(s)://, no whitespace) via validateBaseURL before being saved. On successful save, cached key validity is invalidated so the next validation hits the new endpoint. - Key validation is skipped for local LLM runners (validationRequired is false on their providerDef entries). There is no meaningful way to validate the sentinel "local-llm-no-auth" placeholder against a real endpoint, so we report Configured=true based on URL presence. Frontend changes ---------------- - KeyStatus TS interface gains baseURL, baseURLEnvVar, baseURLSource. - APIKeySettings.tsx adds an Advanced expandable to each row. It only renders when the backend reports baseURLEnvVar (meaning the provider actually supports an override). The form includes a text input, an inline "env var wins" hint when baseURLSource is "env", syntactic validation feedback, and a post-save "restart kc-agent to apply" message. The summary row shows the current resolved value next to the Advanced toggle so operators can see it at a glance. Closes #8254. - go build ./... passes. - go test ./pkg/agent/... passes (flaky auto-update test unrelated). - npm run build passes all post-build safety checks. Signed-off-by: Andrew Anderson <[email protected]> --------- Signed-off-by: Andrew Anderson <[email protected]>
…8314) The nightly Release workflow has been red since 2026-04-13 because four Go tests fell behind handler changes shipped over the past two weeks. Each test now aligns with the current handler contract: - `pkg/agent/server_test.go:TestServer_SettingsHandlers` — expected the keys endpoint to return 0 providers, but #8248/#8254/#8256 now register nine chat-only HTTP providers (3 OpenAI-compatible gateways + 6 local LLM runners) so the Settings modal can show per-provider base URL overrides. Assert non-empty list and Provider presence. - `pkg/api/handlers/cards_test.go:TestRecordFocus_BadBody_Returns400` — `RecordFocus` now runs `requireEditorOrAdmin` before BodyParser (#7011), which calls `store.GetUser`. The test's custom `recordFocusStore` never registered `GetUser`, so the mock panicked. Add the admin-user expectation. - `pkg/api/handlers/dashboard_test.go:setupDashboardTest` — `CreateDashboard` now calls `store.CountUserDashboards` to enforce the per-user dashboard limit (#7010). Register a default `Return(0, nil).Maybe()` so the three CreateDashboard tests stay under the limit. Tests exercising the limit can override. - `pkg/api/handlers/setup_test.go` — `TestClusterGroupsCRUD` exercises handlers that persist definitions via `SaveClusterGroup` / `DeleteClusterGroup` / `ListClusterGroups` (#7013). Register permissive `.Maybe()` mocks on the shared `setupTestEnv` MockStore. Verification: `go test ./...` — all packages pass locally. Fixes #8310, Fixes #8313, Fixes #8314 Signed-off-by: Andy Anderson <[email protected]>
…8314) (#8317) The nightly Release workflow has been red since 2026-04-13 because four Go tests fell behind handler changes shipped over the past two weeks. Each test now aligns with the current handler contract: - `pkg/agent/server_test.go:TestServer_SettingsHandlers` — expected the keys endpoint to return 0 providers, but #8248/#8254/#8256 now register nine chat-only HTTP providers (3 OpenAI-compatible gateways + 6 local LLM runners) so the Settings modal can show per-provider base URL overrides. Assert non-empty list and Provider presence. - `pkg/api/handlers/cards_test.go:TestRecordFocus_BadBody_Returns400` — `RecordFocus` now runs `requireEditorOrAdmin` before BodyParser (#7011), which calls `store.GetUser`. The test's custom `recordFocusStore` never registered `GetUser`, so the mock panicked. Add the admin-user expectation. - `pkg/api/handlers/dashboard_test.go:setupDashboardTest` — `CreateDashboard` now calls `store.CountUserDashboards` to enforce the per-user dashboard limit (#7010). Register a default `Return(0, nil).Maybe()` so the three CreateDashboard tests stay under the limit. Tests exercising the limit can override. - `pkg/api/handlers/setup_test.go` — `TestClusterGroupsCRUD` exercises handlers that persist definitions via `SaveClusterGroup` / `DeleteClusterGroup` / `ListClusterGroups` (#7013). Register permissive `.Maybe()` mocks on the shared `setupTestEnv` MockStore. Verification: `go test ./...` — all packages pass locally. Fixes #8310, Fixes #8313, Fixes #8314 Signed-off-by: Andy Anderson <[email protected]>
Summary
Adds Ollama, llama.cpp, LocalAI, vLLM, LM Studio and Red Hat AI Inference Server as chat-only providers in the agent registry, surfaces them in the agent selector dropdown with icons + install-mission links, and flips on the existing Groq / OpenRouter / Open WebUI providers that had been sitting unregistered.
Chat-vs-missions is respected: local LLM runners report
CapabilityChatonly, sopromoteExecutingDefault()keeps a tool-capable CLI agent as the default for missions. The dropdown honestly reflects which runners are reachable — a runner only shows as available when its URL env var is set (or a loopback default applies for the workstation-first runners).Why
User feedback from Manuela (2026-04-15): "the console uses external LLMs. Although it supports self-hosting, more comprehensive documentation on using local LLMs would be helpful for users in isolated or high-security environments."
Pairs with kubestellar/console-kb#2028 which adds the corresponding install missions, and an upcoming kubestellar/docs page on local-LLM strategy.
What changed
Backend
pkg/agent/provider_local_openai_compat.godefines a genericLocalOpenAICompatProviderparameterized by(name, displayName, urlEnvVar, defaultURL, chatPath)with one factory per runner.OLLAMA_URL,LLAMACPP_URL,LOCALAI_URL,VLLM_URL,LM_STUDIO_URL,RHAIIS_URL. Loopback defaults only for Ollama (127.0.0.1:11434) and LM Studio (127.0.0.1:1234).~/.kc/config.yamlalways win.InitializeProvidershappens AFTER tool-capable CLI agents sopromoteExecutingDefault()still picks a mission-capable default.registry.go:303is expanded to explain the new chat-vs-missions split and why vendor API agents remain unregistered.config.gogains env-var mappings for each new provider key.Frontend
web/src/types/agent.tsaddsollama,llamacpp,localai,vllm,lm-studio,rhaiisto theAgentProviderunion.AgentIcon.tsxadds icon cases for each.agentSelectorUtils.tsaddsLOCAL_LLM_INSTALL_MISSIONSmapping provider name → install mission ID (install-ollama,install-llama-cpp,install-localai,install-vllm,install-lm-studio,install-rhaiis,install-open-webui,install-claude-desktop), and enriches unavailable local-LLM agents withinstallMissionIdso the dropdown surfaces the install mission path when the runner is not yet reachable. Mirrors the kagent/kagenti pattern.Test plan
go build ./...passesgo test ./pkg/agent/...passes (4.2s)npm run buildpasses all post-build safety checksnpm run test -- src/components/agent/AgentSelector.test.tsx— 17 tests passOLLAMA_URL=http://127.0.0.1:11434on a workstation with Ollama running, restart kc-agent, confirm Ollama appears in the dropdown as available🤖 Generated with Claude Code