Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions go/api/v1alpha2/provider_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
Copyright 2025.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha2

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
// ProviderConditionTypeReady indicates whether the provider is ready for use
ProviderConditionTypeReady = "Ready"

// ProviderConditionTypeSecretResolved indicates whether the provider's secret reference is valid
ProviderConditionTypeSecretResolved = "SecretResolved"

// ProviderConditionTypeModelsDiscovered indicates whether model discovery has succeeded
ProviderConditionTypeModelsDiscovered = "ModelsDiscovered"
)

// DefaultProviderEndpoint returns the default API endpoint for a given provider type.
// Returns empty string if no default is defined.
func DefaultProviderEndpoint(providerType ModelProvider) string {
switch providerType {
case ModelProviderOpenAI:
return "https://api.openai.com/v1"
case ModelProviderAnthropic:
return "https://api.anthropic.com"
case ModelProviderGemini:
return "https://generativelanguage.googleapis.com"
case ModelProviderOllama:
return "http://localhost:11434"
default:
// Azure, Bedrock, Vertex AI require user-specific endpoints
return ""
}
}

// SecretReference contains information to locate a secret.
type SecretReference struct {
// Name is the name of the secret in the same namespace as the Provider
// +required
Name string `json:"name"`

// 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.

}

// ProviderSpec defines the desired state of Provider.
//
// +kubebuilder:validation:XValidation: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://')"
// +kubebuilder:validation:XValidation:message="secretRef is 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)"
type ProviderSpec struct {
// Type is the model provider type (OpenAI, Anthropic, etc.)
// +required
// +kubebuilder:validation:Required
Type ModelProvider `json:"type"`

// Endpoint is the API endpoint URL for the provider.
// If not specified, the default endpoint for the provider type will be used.
// +optional
// +kubebuilder:validation:Pattern=`^https?://.*`
Endpoint string `json:"endpoint,omitempty"`

// SecretRef references the Kubernetes Secret containing the API key.
// Optional for providers that don't require authentication (e.g., local Ollama).
// +optional
SecretRef *SecretReference `json:"secretRef,omitempty"`
}

// GetEndpoint returns the endpoint, or the default endpoint if not specified.
func (p *ProviderSpec) GetEndpoint() string {
if p.Endpoint != "" {
return p.Endpoint
}
return DefaultProviderEndpoint(p.Type)
}

// RequiresSecret returns true if this provider type requires a secret for authentication.
func (p *ProviderSpec) RequiresSecret() bool {
return p.Type != ModelProviderOllama
}

// ProviderStatus defines the observed state of Provider.
type ProviderStatus struct {
// ObservedGeneration reflects the generation of the most recently observed Provider spec
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`

// Conditions represent the latest available observations of the Provider's state
// +optional
// +listType=map
// +listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty"`

// DiscoveredModels is the cached list of model IDs available from this provider
// +optional
DiscoveredModels []string `json:"discoveredModels,omitempty"`

// ModelCount is the number of discovered models (for kubectl display)
// +optional
ModelCount int `json:"modelCount,omitempty"`

// LastDiscoveryTime is the timestamp of the last successful model discovery
// +optional
LastDiscoveryTime *metav1.Time `json:"lastDiscoveryTime,omitempty"`

// SecretHash is a hash of the referenced secret data, used to detect secret changes
// +optional
SecretHash string `json:"secretHash,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:resource:categories=kagent,shortName=prov
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".spec.type"
// +kubebuilder:printcolumn:name="Endpoint",type="string",JSONPath=".spec.endpoint"
// +kubebuilder:printcolumn:name="Models",type="integer",JSONPath=".status.modelCount"
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:storageversion

// 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

metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec ProviderSpec `json:"spec,omitempty"`
Status ProviderStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// ProviderList contains a list of Provider.
type ProviderList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Provider `json:"items"`
}

func init() {
SchemeBuilder.Register(&Provider{}, &ProviderList{})
}
125 changes: 125 additions & 0 deletions go/api/v1alpha2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading