Skip to content

Feature: UI dynamic provider model discovery#1274

Open
nujragan93 wants to merge 9 commits intokagent-dev:mainfrom
nujragan93:feature/provider-model-discovery
Open

Feature: UI dynamic provider model discovery#1274
nujragan93 wants to merge 9 commits intokagent-dev:mainfrom
nujragan93:feature/provider-model-discovery

Conversation

@nujragan93
Copy link

@nujragan93 nujragan93 commented Feb 9, 2026

Summary

Introduces a new Provider Custom Resource Definition (CRD) that enables automatic model discovery from LLM providers. Users can configure provider endpoints (e.g., AI gateways, self-hosted LLMs) and kagent will dynamically discover available models.

Changes

Backend (Go)

  • New CRD: Provider type (go/api/v1alpha2/provider_types.go) with fields for provider type, endpoint, and secret reference
  • Controller: ProviderController watches Provider CRs and triggers model discovery
  • Model Discovery: provider/discoverer.go - discovers models from OpenAI, Anthropic, Ollama, Azure, Gemini, and Bedrock APIs
  • Provider Manager: provider/manager.go - caches discovered models and manages provider state
  • API Endpoints:
    • GET /api/providers/configured - list configured providers
    • GET /api/providers/configured/{name}/models - get models for a provider

Frontend (React/Next.js)

  • ProviderCombobox: New dropdown grouping "Stock Providers" and "Configured Providers"
  • ModelCombobox: Displays discovered models with function-calling indicator
  • Dynamic Model Fetching: Fetches models from configured provider endpoints
  • Auto-selection: Defaults to stock OpenAI provider on page load

Infrastructure

  • CRD manifest: kagent.dev_providers.yaml
  • Helm chart templates for CRD and RBAC
  • Sample Provider manifests for OpenAI and Anthropic

Testing

  • Unit tests for model discoverer (17 tests)
  • Unit tests for provider manager
  • Provider selection behavior tests

Nagarjun Krishnan and others added 2 commits February 9, 2026 08:06
@nujragan93 nujragan93 marked this pull request as ready for review February 9, 2026 15:14
@nujragan93
Copy link
Author

Screenshot 2026-02-09 at 8 04 53 AM Screenshot 2026-02-09 at 8 05 01 AM

Copy link
Contributor

@EItanya EItanya left a comment

Choose a reason for hiding this comment

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

These changes are a super exciting first step for this feature! Really excited to see it all coming together. I have some structural changes as well as some nits. I think there will probably be multiple rounds of reviews, but this is a good start.

Nagarjun Krishnan added 3 commits February 11, 2026 09:28
Signed-off-by: Nagarjun Krishnan <nkrishnan@ancestry.com>
Signed-off-by: Nagarjun Krishnan <nkrishnan@ancestry.com>
…/nujragan93/kagent into feature/provider-model-discovery

Signed-off-by: Nagarjun Krishnan <nkrishnan@ancestry.com>
Copilot AI review requested due to automatic review settings February 11, 2026 21:18
Signed-off-by: Nagarjun Krishnan <159933832+nujragan93@users.noreply.github.com>
Copy link
Contributor

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

This PR introduces dynamic model discovery for LLM providers through a new Provider Custom Resource Definition (CRD). It enables users to configure custom provider endpoints (e.g., AI gateways, self-hosted LLMs) and automatically discover available models from those providers.

Changes:

  • Added Provider CRD with controller for automatic model discovery from OpenAI, Anthropic, Ollama, Azure, Gemini, and Bedrock APIs
  • Implemented backend API endpoints to list configured providers and their models
  • Created new React components (ProviderCombobox, ModelCombobox) to support provider and model selection in the UI
  • Added auto-selection of stock OpenAI provider on page load and fixed provider selection persistence bug

Reviewed changes

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

Show a summary per file
File Description
go/api/v1alpha2/provider_types.go Defines Provider CRD structure with spec and status fields
go/internal/controller/provider_controller.go Controller that watches Provider CRs and triggers reconciliation
go/internal/controller/reconciler/reconciler.go Reconciliation logic for model discovery and status updates
go/internal/controller/provider/discoverer.go Model discovery implementation for various LLM providers
go/internal/httpserver/handlers/providers.go API handlers for listing providers and fetching models
go/config/crd/bases/kagent.dev_providers.yaml Base CRD manifest with validation rules
helm/kagent-crds/templates/kagent.dev_providers.yaml Helm template for Provider CRD
ui/src/types/index.ts TypeScript type definitions for providers and models
ui/src/components/ProviderCombobox.tsx New component for provider selection with grouping
ui/src/components/ModelCombobox.tsx New component for model selection
ui/src/components/models/new/BasicInfoSection.tsx Updated to use separate provider and model selection
ui/src/app/models/new/page.tsx Main page logic with auto-fetch and provider management
ui/src/app/actions/providers.ts Server actions for fetching configured providers and models

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

- jsonPath: .spec.endpoint
name: Endpoint
type: string
- jsonPath: .status.discoveredModels[?(@)]
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The JSONPath for the "Models" additionalPrinterColumn is inconsistent with the base CRD. In the base CRD (go/config/crd/bases/kagent.dev_providers.yaml), this uses .status.modelCount, but here it uses .status.discoveredModels[?(@)].

The JSONPath .status.discoveredModels[?(@)] is invalid and will not work correctly. It should be .status.modelCount to match the base CRD definition and to display the count of models as an integer.

Suggested change
- jsonPath: .status.discoveredModels[?(@)]
- jsonPath: .status.modelCount

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +102
- endpoint
- secretRef
- type
type: object
x-kubernetes-validations:
- message: endpoint must be a valid URL starting with http:// or https://
rule: self.endpoint.startsWith('http://') || self.endpoint.startsWith('https://')
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The CRD defines endpoint as a required field (line 96-97), but in the Go API definition (provider_types.go line 77), the Endpoint field is marked as omitempty and has validation rules that explicitly allow it to be empty (line 65: !has(self.endpoint) || self.endpoint == ''). Additionally, the GetEndpoint() method provides default values when the endpoint is not specified.

This creates an inconsistency where the CRD will reject Provider resources without an endpoint, even though the code is designed to support optional endpoints with defaults. The endpoint field should not be in the required list in the CRD.

Suggested change
- endpoint
- secretRef
- type
type: object
x-kubernetes-validations:
- message: endpoint must be a valid URL starting with http:// or https://
rule: self.endpoint.startsWith('http://') || self.endpoint.startsWith('https://')
- secretRef
- type
type: object
x-kubernetes-validations:
- message: endpoint must be a valid URL starting with http:// or https://
rule: "!has(self.endpoint) || self.endpoint == '' || self.endpoint.startsWith('http://') || self.endpoint.startsWith('https://')"

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +105
- endpoint
- secretRef
- type
type: object
x-kubernetes-validations:
- message: endpoint must be a valid URL starting with http:// or https://
rule: self.endpoint.startsWith('http://') || self.endpoint.startsWith('https://')
- message: secretRef.name and secretRef.key are required
rule: has(self.secretRef) && has(self.secretRef.name) && size(self.secretRef.name)
> 0 && has(self.secretRef.key) && size(self.secretRef.key) > 0
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Same issue as in go/config/crd/bases/kagent.dev_providers.yaml: the endpoint and secretRef fields are marked as required, but the Go API definition treats them as optional with defaults. This will prevent users from creating Provider resources without explicitly specifying an endpoint, even though the code supports defaults via the GetEndpoint() method.

Additionally, secretRef should only be required for providers that need authentication (not Ollama), as indicated by the XValidation rule in the Go type definition.

Suggested change
- endpoint
- secretRef
- type
type: object
x-kubernetes-validations:
- message: endpoint must be a valid URL starting with http:// or https://
rule: self.endpoint.startsWith('http://') || self.endpoint.startsWith('https://')
- message: secretRef.name and secretRef.key are required
rule: has(self.secretRef) && has(self.secretRef.name) && size(self.secretRef.name)
> 0 && has(self.secretRef.key) && size(self.secretRef.key) > 0
- type
type: object
x-kubernetes-validations:
- message: endpoint must be a valid URL starting with http:// or https://
rule: !has(self.endpoint) || self.endpoint.startsWith('http://') || self.endpoint.startsWith('https://')
- message: secretRef.name and secretRef.key are required for providers that need authentication (not Ollama)
rule: self.type == 'Ollama' || (has(self.secretRef) && has(self.secretRef.name) && size(self.secretRef.name) > 0 && has(self.secretRef.key) && size(self.secretRef.key) > 0)

Copilot uses AI. Check for mistakes.
Comment on lines +113 to +117
func providerReferencesSecret(provider *v1alpha2.Provider, secretObj types.NamespacedName) bool {
// Secrets must be in the same namespace as the provider
return provider.Namespace == secretObj.Namespace &&
provider.Spec.SecretRef.Name == secretObj.Name
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The function providerReferencesSecret directly accesses provider.Spec.SecretRef.Name without checking if SecretRef is nil. Since SecretRef is an optional pointer field (e.g., for Ollama providers that don't require authentication), this will cause a nil pointer dereference panic when processing providers without a SecretRef.

Add a nil check before accessing SecretRef fields.

Copilot uses AI. Check for mistakes.
Comment on lines 221 to 225
export interface TypedLocalReference {
kind?: string;
apiGroup?: string;
name: string;
namespace?: string;
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The namespace field was removed from the TypedLocalReference interface, but it is still being accessed in ui/src/app/actions/agents.ts (line 72) and ui/src/lib/toolUtils.ts (line 157). This will cause TypeScript compilation errors or runtime issues.

The code attempts to access agent.namespace and tool.agent.namespace, which no longer exist on the type definition. This breaking change needs to be addressed by either:

  1. Keeping the namespace field in TypedLocalReference, or
  2. Updating all code that references it to use an alternative approach

Copilot uses AI. Check for mistakes.

fetchProviderModels();
return () => { isMounted = false; };
}, [selectedProvider, isEditMode, providerModelsData]);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The auto-fetch models effect (lines 279-332) includes providerModelsData in its dependency array (line 332). This creates a problematic cycle:

  1. When a provider is selected, it checks if models exist in providerModelsData
  2. If not, it fetches models and updates providerModelsData (lines 308-311, 323)
  3. The update to providerModelsData triggers the effect to run again

This could lead to unnecessary re-renders and potential infinite loops if the hasModels check (line 288) doesn't work as expected. The dependency array should only include selectedProvider and isEditMode, not providerModelsData.

Suggested change
}, [selectedProvider, isEditMode, providerModelsData]);
}, [selectedProvider, isEditMode]);

Copilot uses AI. Check for mistakes.
github.com/jedib0t/go-pretty/v6 v6.7.8
github.com/kagent-dev/kmcp v0.2.6
github.com/kagent-dev/mockllm v0.0.3
github.com/mark3labs/mcp-go v0.33.0
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The dependency github.com/mark3labs/mcp-go v0.33.0 was added to go.mod but appears to be unused in the codebase. This adds unnecessary bloat to the dependency tree. If this was added by mistake or for future use, it should be removed. If it's actually needed, there should be import statements using it.

Suggested change
github.com/mark3labs/mcp-go v0.33.0

Copilot uses AI. Check for mistakes.
* Fix: Remove `providers` from dependency array so effect only runs on mount.
*/

import { describe, it, expect, jest } from '@jest/globals';
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Unused import jest.

Suggested change
import { describe, it, expect, jest } from '@jest/globals';
import { describe, it, expect } from '@jest/globals';

Copilot uses AI. Check for mistakes.
it('demonstrates the bug: user selection gets overwritten', () => {
const providers = [stockOpenAI, configuredOpenAI];

const { result, rerender } = renderHook(
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Unused variable rerender.

Suggested change
const { result, rerender } = renderHook(
const { result } = renderHook(

Copilot uses AI. Check for mistakes.
@nujragan93 nujragan93 requested a review from EItanya February 11, 2026 22:22
Copy link
Contributor

@EItanya EItanya left a comment

Choose a reason for hiding this comment

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

These changes are looking great! I would love to test it all locally but it looks like there are some build failures


// Provider is the Schema for the providers API.
// It represents a model provider configuration with automatic model discovery.
type Provider struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should rename this to model provider so it's more clear what the purpose is, what do youthink?

Copy link
Author

Choose a reason for hiding this comment

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

Agree, provider seems too vague, I will change this to modelprovider similar to modelconfig


// Key is the key within the secret that contains the API key or credential
// +required
Key string `json:"key"`
Copy link
Contributor

Choose a reason for hiding this comment

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

Another API nit, why is this required? If there's only one key in the secret we can use that. Just small usability things.

return ctrl.Result{}, fmt.Errorf("failed to update provider status: %w", err)
}

// No periodic requeue - discovery only on-demand via HTTP API
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not?

Copy link
Author

Choose a reason for hiding this comment

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

llms behind a provider would not change very frequently, this is why I opted to have a force refresh

nujragan93 and others added 3 commits February 13, 2026 10:41
Signed-off-by: Nagarjun Krishnan <159933832+nujragan93@users.noreply.github.com>
Signed-off-by: Nagarjun Krishnan <nkrishnan@ancestry.com>
Signed-off-by: Nagarjun Krishnan <159933832+nujragan93@users.noreply.github.com>
…/nujragan93/kagent into feature/provider-model-discovery

Signed-off-by: Nagarjun Krishnan <nkrishnan@ancestry.com>
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.

2 participants