diff --git a/go/adk/pkg/agent/agent.go b/go/adk/pkg/agent/agent.go index e1b475240..017f86cd6 100644 --- a/go/adk/pkg/agent/agent.go +++ b/go/adk/pkg/agent/agent.go @@ -4,8 +4,10 @@ import ( "context" "encoding/json" "fmt" + "net/http" "os" "strings" + "time" "github.com/go-logr/logr" "github.com/kagent-dev/kagent/go/adk/pkg/mcp" @@ -57,7 +59,11 @@ func CreateGoogleADKAgentWithSubagentSessionIDs(ctx context.Context, agentConfig log.Info("Skipping remote agent with empty URL", "name", remoteAgent.Name) continue } - remoteTool, sessionID, err := tools.NewKAgentRemoteA2ATool(remoteAgent.Name, remoteAgent.Description, remoteAgent.Url, nil, remoteAgent.Headers) + var httpClient *http.Client + if remoteAgent.Timeout != nil { + httpClient = &http.Client{Timeout: time.Duration(*remoteAgent.Timeout * float64(time.Second))} + } + remoteTool, sessionID, err := tools.NewKAgentRemoteA2ATool(remoteAgent.Name, remoteAgent.Description, remoteAgent.Url, httpClient, remoteAgent.Headers) if err != nil { return nil, nil, fmt.Errorf("failed to create remote A2A tool for %s: %w", remoteAgent.Name, err) } diff --git a/go/api/adk/types.go b/go/api/adk/types.go index aee673f09..4ddb6503c 100644 --- a/go/api/adk/types.go +++ b/go/api/adk/types.go @@ -316,6 +316,7 @@ type RemoteAgentConfig struct { Name string `json:"name"` Url string `json:"url"` Headers map[string]string `json:"headers,omitempty"` + Timeout *float64 `json:"timeout,omitempty"` Description string `json:"description,omitempty"` } diff --git a/go/api/v1alpha2/agent_types.go b/go/api/v1alpha2/agent_types.go index db57fe3cd..5df062c77 100644 --- a/go/api/v1alpha2/agent_types.go +++ b/go/api/v1alpha2/agent_types.go @@ -445,6 +445,10 @@ type TypedReference struct { Name string `json:"name"` // +optional Namespace string `json:"namespace,omitempty"` + // Timeout specifies the A2A client read timeout in seconds for calls to this agent. + // Defaults to 600s if unset. + // +optional + Timeout *float64 `json:"timeout,omitempty"` } func (t *TypedReference) GroupKind() schema.GroupKind { diff --git a/go/core/internal/controller/translator/agent/adk_api_translator.go b/go/core/internal/controller/translator/agent/adk_api_translator.go index 47f0c80be..ddb9b5b49 100644 --- a/go/core/internal/controller/translator/agent/adk_api_translator.go +++ b/go/core/internal/controller/translator/agent/adk_api_translator.go @@ -745,12 +745,16 @@ func (a *adkApiTranslator) translateInlineAgent(ctx context.Context, agent *v1al } } - cfg.RemoteAgents = append(cfg.RemoteAgents, adk.RemoteAgentConfig{ + remoteAgent := adk.RemoteAgentConfig{ Name: utils.ConvertToPythonIdentifier(utils.GetObjectRef(toolAgent)), Url: targetURL, Headers: headers, Description: toolAgent.Spec.Description, - }) + } + if tool.Agent.Timeout != nil { + remoteAgent.Timeout = tool.Agent.Timeout + } + cfg.RemoteAgents = append(cfg.RemoteAgents, remoteAgent) default: return nil, nil, nil, fmt.Errorf("unknown agent type: %s", toolAgent.Spec.Type) } diff --git a/go/core/internal/controller/translator/agent/adk_api_translator_test.go b/go/core/internal/controller/translator/agent/adk_api_translator_test.go index 4344f68b2..40c2378cd 100644 --- a/go/core/internal/controller/translator/agent/adk_api_translator_test.go +++ b/go/core/internal/controller/translator/agent/adk_api_translator_test.go @@ -193,6 +193,94 @@ func Test_AdkApiTranslator_CrossNamespaceAgentTool(t *testing.T) { } } +// Test_AdkApiTranslator_AgentToolTimeout tests that the timeout field on a +// TypedReference is correctly propagated into the RemoteAgentConfig produced +// by the translator. +func Test_AdkApiTranslator_AgentToolTimeout(t *testing.T) { + scheme := schemev1.Scheme + require.NoError(t, v1alpha2.AddToScheme(scheme)) + + timeout1800 := float64(1800) + + modelConfig := &v1alpha2.ModelConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "test-model", Namespace: "default"}, + Spec: v1alpha2.ModelConfigSpec{Model: "gpt-4", Provider: v1alpha2.ModelProviderOpenAI}, + } + toolAgent := &v1alpha2.Agent{ + ObjectMeta: metav1.ObjectMeta{Name: "slow-agent", Namespace: "default"}, + Spec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_Declarative, + Description: "Slow agent", + Declarative: &v1alpha2.DeclarativeAgentSpec{ + SystemMessage: "You are slow", + ModelConfig: "test-model", + }, + }, + } + + tests := []struct { + name string + timeout *float64 + wantTimeout *float64 + }{ + { + name: "timeout set - propagated into RemoteAgentConfig", + timeout: &timeout1800, + wantTimeout: &timeout1800, + }, + { + name: "timeout nil - RemoteAgentConfig has no timeout", + timeout: nil, + wantTimeout: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sourceAgent := &v1alpha2.Agent{ + ObjectMeta: metav1.ObjectMeta{Name: "source-agent", Namespace: "default"}, + Spec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_Declarative, + Description: "Source agent", + Declarative: &v1alpha2.DeclarativeAgentSpec{ + SystemMessage: "You are the source", + ModelConfig: "test-model", + Tools: []*v1alpha2.Tool{ + { + Type: v1alpha2.ToolProviderType_Agent, + Agent: &v1alpha2.TypedReference{ + Name: "slow-agent", + Namespace: "default", + Timeout: tt.timeout, + }, + }, + }, + }, + }, + } + + kubeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(modelConfig, toolAgent, sourceAgent). + Build() + + trans := translator.NewAdkApiTranslator(kubeClient, types.NamespacedName{Namespace: "default", Name: "test-model"}, nil, "") + outputs, err := trans.TranslateAgent(context.Background(), sourceAgent) + require.NoError(t, err) + require.NotNil(t, outputs.Config) + require.Len(t, outputs.Config.RemoteAgents, 1) + + got := outputs.Config.RemoteAgents[0].Timeout + if tt.wantTimeout == nil { + assert.Nil(t, got) + } else { + require.NotNil(t, got) + assert.Equal(t, *tt.wantTimeout, *got) + } + }) + } +} + // Test_AdkApiTranslator_CrossNamespaceRemoteMCPServer tests that the translator // can handle cross-namespace RemoteMCPServer references. Note that cross-namespace // validation (AllowedNamespaces checks) is now done in the reconciler, diff --git a/helm/kagent-crds/templates/kagent.dev_agents.yaml b/helm/kagent-crds/templates/kagent.dev_agents.yaml index a34ced941..4bd0811c6 100644 --- a/helm/kagent-crds/templates/kagent.dev_agents.yaml +++ b/helm/kagent-crds/templates/kagent.dev_agents.yaml @@ -2238,6 +2238,11 @@ spec: Can either be a reference to the name of an Agent in the same namespace as the referencing Agent, or a reference to the name of an Agent in a different namespace in the form / minLength: 1 type: string + timeout: + description: A2A client read timeout in seconds for calls + to this agent. Defaults to 600s if unset. + format: double + type: number type: object mcpServer: properties: