这是indexloc提供的服务,不要输入任何密码
Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
.idea/
**/CLAUDE.local.md
*__debug_bin*

# macOS system files
.DS_Store
**/.DS_Store

4 changes: 4 additions & 0 deletions kubechain/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ go.work
*.swp
*.swo
*~

# macOS system files
.DS_Store
**/.DS_Store
18 changes: 18 additions & 0 deletions kubechain/api/v1alpha1/agent_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ type AgentSpec struct {
// +optional
MCPServers []LocalObjectReference `json:"mcpServers,omitempty"`

// HumanContactChannels is a list of ContactChannel resources that can be used for human interactions
// +optional
HumanContactChannels []LocalObjectReference `json:"humanContactChannels,omitempty"`

// System is the system prompt for the agent
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
Expand Down Expand Up @@ -51,6 +55,10 @@ type AgentStatus struct {
// ValidMCPServers is the list of MCP servers that were successfully validated
// +optional
ValidMCPServers []ResolvedMCPServer `json:"validMCPServers,omitempty"`

// ValidHumanContactChannels is the list of human contact channels that were successfully validated
// +optional
ValidHumanContactChannels []ResolvedContactChannel `json:"validHumanContactChannels,omitempty"`
}

type ResolvedTool struct {
Expand All @@ -73,6 +81,16 @@ type ResolvedMCPServer struct {
Tools []string `json:"tools,omitempty"`
}

type ResolvedContactChannel struct {
// Name of the contact channel
// +kubebuilder:validation:Required
Name string `json:"name"`

// Type of the contact channel (e.g., "slack", "email")
// +kubebuilder:validation:Required
Type string `json:"type"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Ready",type="boolean",JSONPath=".status.ready"
Expand Down
4 changes: 4 additions & 0 deletions kubechain/api/v1alpha1/taskruntoolcall_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ type TaskRunToolCallSpec struct {
// +kubebuilder:validation:Required
ToolRef LocalObjectReference `json:"toolRef"`

// ToolType identifies the type of the tool (Standard, MCP, HumanContact)
// +optional
ToolType ToolType `json:"toolType,omitempty"`

// Arguments contains the arguments for the tool call
// +kubebuilder:validation:Required
Arguments string `json:"arguments"`
Expand Down
13 changes: 13 additions & 0 deletions kubechain/api/v1alpha1/tool_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)

// ToolType defines the type of a tool in the system
// +kubebuilder:validation:Enum=Standard;MCP;HumanContact
type ToolType string

const (
// ToolTypeStandard indicates a standard tool defined in the system
ToolTypeStandard ToolType = "Standard"
// ToolTypeMCP indicates a tool provided by an MCP server
ToolTypeMCP ToolType = "MCP"
// ToolTypeHumanContact indicates a tool for human interaction
ToolTypeHumanContact ToolType = "HumanContact"
)

// ToolSpec defines the desired state of Tool
type ToolSpec struct {
// Name is used for inline/function tools (optional if the object name is used).
Expand Down
25 changes: 25 additions & 0 deletions kubechain/api/v1alpha1/zz_generated.deepcopy.go

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

31 changes: 31 additions & 0 deletions kubechain/config/crd/bases/kubechain.humanlayer.dev_agents.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,21 @@ spec:
spec:
description: AgentSpec defines the desired state of Agent
properties:
humanContactChannels:
description: HumanContactChannels is a list of ContactChannel resources
that can be used for human interactions
items:
description: LocalObjectReference contains enough information to
locate the referenced resource in the same namespace
properties:
name:
description: Name of the referent
minLength: 1
type: string
required:
- name
type: object
type: array
llmRef:
description: LLMRef references the LLM to use for this agent
properties:
Expand Down Expand Up @@ -114,6 +129,22 @@ spec:
description: StatusDetail provides additional details about the current
status
type: string
validHumanContactChannels:
description: ValidHumanContactChannels is the list of human contact
channels that were successfully validated
items:
properties:
name:
description: Name of the contact channel
type: string
type:
description: Type of the contact channel (e.g., "slack", "email")
type: string
required:
- name
- type
type: object
type: array
validMCPServers:
description: ValidMCPServers is the list of MCP servers that were
successfully validated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ spec:
required:
- name
type: object
toolType:
description: ToolType identifies the type of the tool (Standard, MCP,
HumanContact)
enum:
- Standard
- MCP
- HumanContact
type: string
required:
- arguments
- taskRunRef
Expand Down
2 changes: 1 addition & 1 deletion kubechain/config/manager/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ kind: Kustomization
images:
- name: controller
newName: controller
newTag: "202504041032"
newTag: "202504041316"
5 changes: 3 additions & 2 deletions kubechain/internal/adapters/mcp_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ func ConvertMCPToolsToLLMClientTools(mcpTools []kubechainv1alpha1.MCPTool, serve

// Create the tool with the function definition
clientTools = append(clientTools, llmclient.Tool{
Type: "function",
Function: toolFunction,
Type: "function",
Function: toolFunction,
KubechainToolType: kubechainv1alpha1.ToolTypeMCP, // Set as MCP type
})
}

Expand Down
79 changes: 77 additions & 2 deletions kubechain/internal/controller/agent/agent_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const (
// +kubebuilder:rbac:groups=kubechain.humanlayer.dev,resources=llms,verbs=get;list;watch
// +kubebuilder:rbac:groups=kubechain.humanlayer.dev,resources=tools,verbs=get;list;watch
// +kubebuilder:rbac:groups=kubechain.humanlayer.dev,resources=mcpservers,verbs=get;list;watch
// +kubebuilder:rbac:groups=kubechain.humanlayer.dev,resources=contactchannels,verbs=get;list;watch

// AgentReconciler reconciles a Agent object
type AgentReconciler struct {
Expand Down Expand Up @@ -121,6 +122,54 @@ func (r *AgentReconciler) validateMCPServers(ctx context.Context, agent *kubecha
return validMCPServers, nil
}

// validateHumanContactChannels checks if all referenced contact channels exist and are ready
// and have the required context information for the LLM
func (r *AgentReconciler) validateHumanContactChannels(ctx context.Context, agent *kubechainv1alpha1.Agent) ([]kubechainv1alpha1.ResolvedContactChannel, error) {
validChannels := make([]kubechainv1alpha1.ResolvedContactChannel, 0, len(agent.Spec.HumanContactChannels))

for _, channelRef := range agent.Spec.HumanContactChannels {
channel := &kubechainv1alpha1.ContactChannel{}
err := r.Get(ctx, client.ObjectKey{
Namespace: agent.Namespace,
Name: channelRef.Name,
}, channel)
if err != nil {
return validChannels, fmt.Errorf("failed to get ContactChannel %q: %w", channelRef.Name, err)
}

if !channel.Status.Ready {
return validChannels, fmt.Errorf("ContactChannel %q is not ready", channelRef.Name)
}

// Check that the context about the user/channel is provided based on the channel type
switch channel.Spec.Type {
case kubechainv1alpha1.ContactChannelTypeEmail:
if channel.Spec.Email == nil {
return validChannels, fmt.Errorf("ContactChannel %q is missing Email configuration", channelRef.Name)
}
if channel.Spec.Email.ContextAboutUser == "" {
return validChannels, fmt.Errorf("ContactChannel %q must have ContextAboutUser set", channelRef.Name)
}
case kubechainv1alpha1.ContactChannelTypeSlack:
if channel.Spec.Slack == nil {
return validChannels, fmt.Errorf("ContactChannel %q is missing Slack configuration", channelRef.Name)
}
if channel.Spec.Slack.ContextAboutChannelOrUser == "" {
return validChannels, fmt.Errorf("ContactChannel %q must have ContextAboutChannelOrUser set", channelRef.Name)
}
default:
return validChannels, fmt.Errorf("ContactChannel %q has unsupported type %q", channelRef.Name, channel.Spec.Type)
}

validChannels = append(validChannels, kubechainv1alpha1.ResolvedContactChannel{
Name: channelRef.Name,
Type: string(channel.Spec.Type),
})
}

return validChannels, nil
}

// Reconcile validates the agent's LLM and Tool references
func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
Expand All @@ -142,9 +191,10 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
r.recorder.Event(&agent, corev1.EventTypeNormal, "Initializing", "Starting validation")
}

// Initialize empty valid tools and servers slices
// Initialize empty valid tools, servers, and human contact channels slices
validTools := make([]kubechainv1alpha1.ResolvedTool, 0)
validMCPServers := make([]kubechainv1alpha1.ResolvedMCPServer, 0)
validHumanContactChannels := make([]kubechainv1alpha1.ResolvedContactChannel, 0)

// Validate LLM reference
if err := r.validateLLM(ctx, &agent); err != nil {
Expand All @@ -154,6 +204,7 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
statusUpdate.Status.StatusDetail = err.Error()
statusUpdate.Status.ValidTools = validTools
statusUpdate.Status.ValidMCPServers = validMCPServers
statusUpdate.Status.ValidHumanContactChannels = validHumanContactChannels
r.recorder.Event(&agent, corev1.EventTypeWarning, "ValidationFailed", err.Error())
if updateErr := r.Status().Update(ctx, statusUpdate); updateErr != nil {
logger.Error(updateErr, "Failed to update Agent status")
Expand All @@ -171,6 +222,7 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
statusUpdate.Status.StatusDetail = err.Error()
statusUpdate.Status.ValidTools = validTools
statusUpdate.Status.ValidMCPServers = validMCPServers
statusUpdate.Status.ValidHumanContactChannels = validHumanContactChannels
r.recorder.Event(&agent, corev1.EventTypeWarning, "ValidationFailed", err.Error())
if updateErr := r.Status().Update(ctx, statusUpdate); updateErr != nil {
logger.Error(updateErr, "Failed to update Agent status")
Expand All @@ -189,6 +241,27 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
statusUpdate.Status.StatusDetail = err.Error()
statusUpdate.Status.ValidTools = validTools
statusUpdate.Status.ValidMCPServers = validMCPServers
statusUpdate.Status.ValidHumanContactChannels = validHumanContactChannels
r.recorder.Event(&agent, corev1.EventTypeWarning, "ValidationFailed", err.Error())
if updateErr := r.Status().Update(ctx, statusUpdate); updateErr != nil {
logger.Error(updateErr, "Failed to update Agent status")
return ctrl.Result{}, fmt.Errorf("failed to update agent status: %v", err)
}
return ctrl.Result{}, err // requeue
}
}

// Validate HumanContactChannel references, if any
if len(agent.Spec.HumanContactChannels) > 0 {
validHumanContactChannels, err = r.validateHumanContactChannels(ctx, &agent)
if err != nil {
logger.Error(err, "HumanContactChannel validation failed")
statusUpdate.Status.Ready = false
statusUpdate.Status.Status = StatusError
statusUpdate.Status.StatusDetail = err.Error()
statusUpdate.Status.ValidTools = validTools
statusUpdate.Status.ValidMCPServers = validMCPServers
statusUpdate.Status.ValidHumanContactChannels = validHumanContactChannels
r.recorder.Event(&agent, corev1.EventTypeWarning, "ValidationFailed", err.Error())
if updateErr := r.Status().Update(ctx, statusUpdate); updateErr != nil {
logger.Error(updateErr, "Failed to update Agent status")
Expand All @@ -204,6 +277,7 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
statusUpdate.Status.StatusDetail = "All dependencies validated successfully"
statusUpdate.Status.ValidTools = validTools
statusUpdate.Status.ValidMCPServers = validMCPServers
statusUpdate.Status.ValidHumanContactChannels = validHumanContactChannels
r.recorder.Event(&agent, corev1.EventTypeNormal, "ValidationSucceeded", "All dependencies validated successfully")

// Update status
Expand All @@ -216,7 +290,8 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
"name", agent.Name,
"ready", statusUpdate.Status.Ready,
"status", statusUpdate.Status.Status,
"validTools", statusUpdate.Status.ValidTools)
"validTools", statusUpdate.Status.ValidTools,
"validHumanContactChannels", statusUpdate.Status.ValidHumanContactChannels)
return ctrl.Result{}, nil
}

Expand Down
Loading
Loading