From 382e228f40fe79c3e5cc5ed1578d891df0cb1b2b Mon Sep 17 00:00:00 2001 From: Jet Chiang Date: Fri, 22 May 2026 17:42:42 -0400 Subject: [PATCH 1/8] all controller changes combined into one commit for version 0.10.0 Signed-off-by: Jet Chiang --- go/api/client/agent.go | 23 +- go/api/database/client.go | 14 +- go/api/database/models.go | 48 +- go/core/internal/a2a/a2a_handler_mux.go | 42 +- go/core/internal/a2a/a2a_registrar.go | 98 +-- go/core/internal/a2a/a2a_utils.go | 11 +- go/core/internal/a2a/client_interceptors.go | 66 ++ go/core/internal/a2a/manager.go | 70 +-- go/core/internal/a2a/trace.go | 14 - go/core/internal/a2a/trace_test.go | 56 -- .../controller/translator/agent/compiler.go | 4 +- .../translator/agent/manifest_builder.go | 4 +- .../outputs/agent_with_a2a_config.json | 20 +- .../outputs/agent_with_allowed_headers.json | 20 +- .../testdata/outputs/agent_with_code.json | 20 +- .../outputs/agent_with_context_config.json | 20 +- .../agent_with_cross_namespace_tools.json | 20 +- .../outputs/agent_with_custom_sa.json | 20 +- .../outputs/agent_with_default_sa.json | 20 +- .../agent_with_embedding_provider.json | 20 +- .../outputs/agent_with_extra_containers.json | 20 +- .../outputs/agent_with_git_skills.json | 20 +- .../outputs/agent_with_http_toolserver.json | 20 +- .../outputs/agent_with_mcp_service.json | 20 +- .../testdata/outputs/agent_with_memory.json | 20 +- .../outputs/agent_with_nested_agent.json | 20 +- .../outputs/agent_with_passthrough.json | 20 +- .../outputs/agent_with_prompt_template.json | 20 +- .../testdata/outputs/agent_with_proxy.json | 20 +- .../agent_with_proxy_external_remotemcp.json | 20 +- .../outputs/agent_with_proxy_mcpserver.json | 20 +- ...t_with_proxy_mcpserver_custom_timeout.json | 20 +- .../outputs/agent_with_proxy_service.json | 20 +- .../outputs/agent_with_require_approval.json | 20 +- .../agent_with_scheduling_attributes.json | 20 +- .../outputs/agent_with_security_context.json | 20 +- .../testdata/outputs/agent_with_skills.json | 20 +- .../outputs/agent_with_streaming.json | 20 +- ...nt_with_system_message_from_configmap.json | 20 +- ...agent_with_system_message_from_secret.json | 20 +- .../testdata/outputs/anthropic_agent.json | 20 +- .../agent/testdata/outputs/basic_agent.json | 20 +- .../agent/testdata/outputs/bedrock_agent.json | 20 +- .../agent/testdata/outputs/ollama_agent.json | 20 +- .../testdata/outputs/tls-with-custom-ca.json | 20 +- .../outputs/tls-with-disabled-verify.json | 20 +- .../outputs/tls-with-system-cas-disabled.json | 20 +- .../controller/translator/agent/utils.go | 62 +- .../controller/translator/agent/utils_test.go | 28 +- go/core/internal/database/client_postgres.go | 126 ++-- go/core/internal/database/client_test.go | 4 +- go/core/internal/database/gen/models.go | 26 +- .../database/gen/push_notifications.sql.go | 29 +- go/core/internal/database/gen/tasks.sql.go | 31 +- .../database/queries/push_notifications.sql | 9 +- go/core/internal/database/queries/tasks.sql | 11 +- .../httpserver/handlers/a2a_version.go | 29 + .../internal/httpserver/handlers/agents.go | 46 +- .../httpserver/handlers/agents_test.go | 86 --- .../internal/httpserver/handlers/sessions.go | 31 +- .../httpserver/handlers/sessions_test.go | 9 +- go/core/internal/httpserver/handlers/tasks.go | 80 ++- go/core/internal/mcp/mcp_handler.go | 100 ++-- go/core/pkg/a2acompat/trpcv0/convert.go | 565 ++++++++++++++++++ go/core/pkg/a2acompat/trpcv0/convert_test.go | 385 ++++++++++++ go/core/pkg/env/kagent.go | 10 - .../core/000005_a2a_protocol_version.down.sql | 2 + .../core/000005_a2a_protocol_version.up.sql | 2 + .../translator/adk_api_translator_types.go | 4 +- go/core/test/e2e/invoke_api_test.go | 187 +++--- go/go.mod | 21 +- go/go.sum | 36 +- 72 files changed, 2209 insertions(+), 860 deletions(-) create mode 100644 go/core/internal/a2a/client_interceptors.go create mode 100644 go/core/internal/httpserver/handlers/a2a_version.go create mode 100644 go/core/pkg/a2acompat/trpcv0/convert.go create mode 100644 go/core/pkg/a2acompat/trpcv0/convert_test.go create mode 100644 go/core/pkg/migrations/core/000005_a2a_protocol_version.down.sql create mode 100644 go/core/pkg/migrations/core/000005_a2a_protocol_version.up.sql diff --git a/go/api/client/agent.go b/go/api/client/agent.go index d6bd0dd1ab..1a08c445ab 100644 --- a/go/api/client/agent.go +++ b/go/api/client/agent.go @@ -3,7 +3,6 @@ package client import ( "context" "fmt" - "net/url" api "github.com/kagent-dev/kagent/go/api/httpapi" "github.com/kagent-dev/kagent/go/api/v1alpha2" @@ -11,18 +10,13 @@ import ( // Agent defines the agent operations type Agent interface { - ListAgents(ctx context.Context, opts ...ListAgentsOptions) (*api.StandardResponse[[]api.AgentResponse], error) + ListAgents(ctx context.Context) (*api.StandardResponse[[]api.AgentResponse], error) CreateAgent(ctx context.Context, request *v1alpha2.Agent) (*api.StandardResponse[*v1alpha2.Agent], error) GetAgent(ctx context.Context, agentRef string) (*api.StandardResponse[*api.AgentResponse], error) UpdateAgent(ctx context.Context, request *v1alpha2.Agent) (*api.StandardResponse[*v1alpha2.Agent], error) DeleteAgent(ctx context.Context, agentRef string) error } -// ListAgentsOptions configures ListAgents requests. -type ListAgentsOptions struct { - Namespace string -} - // agentClient handles agent-related requests type agentClient struct { client *BaseClient @@ -33,23 +27,14 @@ func NewAgentClient(client *BaseClient) Agent { return &agentClient{client: client} } -// ListAgents lists all agents for a user. When Namespace is set, only agents in that namespace are returned. -func (c *agentClient) ListAgents(ctx context.Context, opts ...ListAgentsOptions) (*api.StandardResponse[[]api.AgentResponse], error) { - if len(opts) > 1 { - return nil, fmt.Errorf("ListAgents accepts at most one options argument") - } - +// ListAgents lists all agents for a user +func (c *agentClient) ListAgents(ctx context.Context) (*api.StandardResponse[[]api.AgentResponse], error) { userID := c.client.GetUserIDOrDefault("") if userID == "" { return nil, fmt.Errorf("userID is required") } - path := "/api/agents" - if len(opts) > 0 && opts[0].Namespace != "" { - path += "?namespace=" + url.QueryEscape(opts[0].Namespace) - } - - resp, err := c.client.Get(ctx, path, userID) + resp, err := c.client.Get(ctx, "/api/agents", userID) if err != nil { return nil, err } diff --git a/go/api/database/client.go b/go/api/database/client.go index 43943da678..312db52d95 100644 --- a/go/api/database/client.go +++ b/go/api/database/client.go @@ -4,9 +4,9 @@ import ( "context" "time" + a2a "github.com/a2aproject/a2a-go/v2/a2a" "github.com/kagent-dev/kagent/go/api/v1alpha2" "github.com/pgvector/pgvector-go" - "trpc.group/trpc-go/trpc-a2a-go/protocol" ) type QueryOptions struct { @@ -24,8 +24,8 @@ type Client interface { StoreFeedback(ctx context.Context, feedback *Feedback) error StoreSession(ctx context.Context, session *Session) error StoreAgent(ctx context.Context, agent *Agent) error - StoreTask(ctx context.Context, task *protocol.Task) error - StorePushNotification(ctx context.Context, config *protocol.TaskPushNotificationConfig) error + StoreTask(ctx context.Context, task *a2a.Task) error + StorePushNotification(ctx context.Context, config *a2a.PushConfig) error StoreToolServer(ctx context.Context, toolServer *ToolServer) (*ToolServer, error) StoreEvents(ctx context.Context, messages ...*Event) error @@ -40,15 +40,15 @@ type Client interface { // Get methods GetSession(ctx context.Context, sessionID string, userID string) (*Session, error) GetAgent(ctx context.Context, name string) (*Agent, error) - GetTask(ctx context.Context, id string) (*protocol.Task, error) + GetTask(ctx context.Context, id string) (*a2a.Task, error) GetTool(ctx context.Context, name string) (*Tool, error) GetToolServer(ctx context.Context, name string) (*ToolServer, error) - GetPushNotification(ctx context.Context, taskID string, configID string) (*protocol.TaskPushNotificationConfig, error) + GetPushNotification(ctx context.Context, taskID string, configID string) (*a2a.PushConfig, error) // List methods ListTools(ctx context.Context) ([]Tool, error) ListFeedback(ctx context.Context, userID string) ([]Feedback, error) - ListTasksForSession(ctx context.Context, sessionID string) ([]*protocol.Task, error) + ListTasksForSession(ctx context.Context, sessionID string) ([]*a2a.Task, error) ListSessions(ctx context.Context, userID string) ([]Session, error) ListSessionsForAgent(ctx context.Context, agentID string, userID string) ([]Session, error) ListSessionsForAgentAllUsers(ctx context.Context, agentID string) ([]Session, error) @@ -56,7 +56,7 @@ type Client interface { ListToolServers(ctx context.Context) ([]ToolServer, error) ListToolsForServer(ctx context.Context, serverName string, groupKind string) ([]Tool, error) ListEventsForSession(ctx context.Context, sessionID, userID string, options QueryOptions) ([]*Event, error) - ListPushNotifications(ctx context.Context, taskID string) ([]*protocol.TaskPushNotificationConfig, error) + ListPushNotifications(ctx context.Context, taskID string) ([]*a2a.PushConfig, error) // Helper methods RefreshToolsForServer(ctx context.Context, serverName string, groupKind string, tools ...*v1alpha2.MCPTool) error diff --git a/go/api/database/models.go b/go/api/database/models.go index 7bb7be9daa..d0852fe922 100644 --- a/go/api/database/models.go +++ b/go/api/database/models.go @@ -4,10 +4,10 @@ import ( "encoding/json" "time" + a2a "github.com/a2aproject/a2a-go/v2/a2a" "github.com/kagent-dev/kagent/go/api/adk" "github.com/kagent-dev/kagent/go/api/v1alpha2" "github.com/pgvector/pgvector-go" - "trpc.group/trpc-go/trpc-a2a-go/protocol" ) type Agent struct { @@ -32,16 +32,16 @@ type Event struct { Data string `json:"data"` // JSON-serialized protocol.Message } -func (m *Event) Parse() (protocol.Message, error) { - var data protocol.Message +func (m *Event) Parse() (a2a.Message, error) { + var data a2a.Message if err := json.Unmarshal([]byte(m.Data), &data); err != nil { - return protocol.Message{}, err + return a2a.Message{}, err } return data, nil } -func ParseMessages(messages []Event) ([]*protocol.Message, error) { - result := make([]*protocol.Message, 0, len(messages)) +func ParseMessages(messages []Event) ([]*a2a.Message, error) { + result := make([]*a2a.Message, 0, len(messages)) for _, message := range messages { parsed, err := message.Parse() if err != nil { @@ -77,24 +77,25 @@ type Session struct { } type Task struct { - ID string `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - DeletedAt *time.Time `json:"deleted_at,omitempty"` - Data string `json:"data"` // JSON-serialized task data - SessionID string `json:"session_id"` + ID string `json:"id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt *time.Time `json:"deleted_at,omitempty"` + Data string `json:"data"` // JSON-serialized task data + ProtocolVersion *string `json:"protocol_version,omitempty"` + SessionID string `json:"session_id"` } -func (t *Task) Parse() (protocol.Task, error) { - var data protocol.Task +func (t *Task) Parse() (a2a.Task, error) { + var data a2a.Task if err := json.Unmarshal([]byte(t.Data), &data); err != nil { - return protocol.Task{}, err + return a2a.Task{}, err } return data, nil } -func ParseTasks(tasks []Task) ([]*protocol.Task, error) { - result := make([]*protocol.Task, 0, len(tasks)) +func ParseTasks(tasks []Task) ([]*a2a.Task, error) { + result := make([]*a2a.Task, 0, len(tasks)) for _, task := range tasks { parsed, err := task.Parse() if err != nil { @@ -106,12 +107,13 @@ func ParseTasks(tasks []Task) ([]*protocol.Task, error) { } type PushNotification struct { - ID string `json:"id"` - TaskID string `json:"task_id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - DeletedAt *time.Time `json:"deleted_at,omitempty"` - Data string `json:"data"` // JSON-serialized push notification config + ID string `json:"id"` + TaskID string `json:"task_id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt *time.Time `json:"deleted_at,omitempty"` + Data string `json:"data"` // JSON-serialized push notification config + ProtocolVersion *string `json:"protocol_version,omitempty"` } // FeedbackIssueType represents the category of feedback issue diff --git a/go/core/internal/a2a/a2a_handler_mux.go b/go/core/internal/a2a/a2a_handler_mux.go index 3d4bb7722e..af35cfb180 100644 --- a/go/core/internal/a2a/a2a_handler_mux.go +++ b/go/core/internal/a2a/a2a_handler_mux.go @@ -6,21 +6,22 @@ import ( "strings" "sync" + a2atype "github.com/a2aproject/a2a-go/v2/a2a" + a2aclient "github.com/a2aproject/a2a-go/v2/a2aclient" + "github.com/a2aproject/a2a-go/v2/a2asrv" "github.com/gorilla/mux" authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" common "github.com/kagent-dev/kagent/go/core/internal/utils" "github.com/kagent-dev/kagent/go/core/pkg/auth" - "trpc.group/trpc-go/trpc-a2a-go/client" - "trpc.group/trpc-go/trpc-a2a-go/server" ) // A2AHandlerMux is an interface that defines methods for adding, getting, and removing agentic task handlers. type A2AHandlerMux interface { SetAgentHandler( agentRef string, - client *client.A2AClient, - card server.AgentCard, - tracing server.Middleware, + client *a2aclient.Client, + card a2atype.AgentCard, + tracing middleware, ) error RemoveAgentHandler( agentRef string, @@ -38,6 +39,10 @@ type handlerMux struct { var _ A2AHandlerMux = &handlerMux{} +type middleware interface { + Wrap(next http.Handler) http.Handler +} + func NewA2AHttpMux(agentPathPrefix, sandboxPathPrefix string, authenticator auth.AuthProvider) *handlerMux { return &handlerMux{ handlers: make(map[string]http.Handler), @@ -49,23 +54,34 @@ func NewA2AHttpMux(agentPathPrefix, sandboxPathPrefix string, authenticator auth func (a *handlerMux) SetAgentHandler( agentRef string, - client *client.A2AClient, - card server.AgentCard, - tracing server.Middleware, + client *a2aclient.Client, + card a2atype.AgentCard, + tracing middleware, ) error { - middlewares := []server.Middleware{authimpl.NewA2AAuthenticator(a.authenticator)} + requestHandler := a2asrv.NewHandler(NewPassthroughExecutor(client)) + jsonrpcHandler := a2asrv.NewJSONRPCHandler(requestHandler) + cardHandler := a2asrv.NewStaticAgentCardHandler(&card) + wellKnownPath := "/" + strings.TrimPrefix(a2asrv.WellKnownAgentCardPath, "/") + + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasSuffix(r.URL.Path, wellKnownPath) { + cardHandler.ServeHTTP(w, r) + return + } + jsonrpcHandler.ServeHTTP(w, r) + }) + middlewares := []middleware{authimpl.NewA2AAuthenticator(a.authenticator)} if tracing != nil { middlewares = append(middlewares, tracing) } - srv, err := server.NewA2AServer(card, NewPassthroughManager(client), server.WithMiddleWare(middlewares...)) - if err != nil { - return fmt.Errorf("failed to create A2A server: %w", err) + for i := len(middlewares) - 1; i >= 0; i-- { + handler = middlewares[i].Wrap(handler) } a.lock.Lock() defer a.lock.Unlock() - a.handlers[agentRef] = srv.Handler() + a.handlers[agentRef] = handler return nil } diff --git a/go/core/internal/a2a/a2a_registrar.go b/go/core/internal/a2a/a2a_registrar.go index d21dbec040..3a696f5d74 100644 --- a/go/core/internal/a2a/a2a_registrar.go +++ b/go/core/internal/a2a/a2a_registrar.go @@ -8,10 +8,11 @@ import ( "reflect" "time" + a2atype "github.com/a2aproject/a2a-go/v2/a2a" + a2aclient "github.com/a2aproject/a2a-go/v2/a2aclient" "github.com/go-logr/logr" "github.com/kagent-dev/kagent/go/api/v1alpha2" agent_translator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent" - authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" common "github.com/kagent-dev/kagent/go/core/internal/utils" "github.com/kagent-dev/kagent/go/core/pkg/auth" "github.com/kagent-dev/kagent/go/core/pkg/env" @@ -20,16 +21,14 @@ import ( crcache "sigs.k8s.io/controller-runtime/pkg/cache" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/manager" - a2aclient "trpc.group/trpc-go/trpc-a2a-go/client" ) type A2ARegistrar struct { - cache crcache.Cache - handlerMux A2AHandlerMux - a2aBaseURL string - sandboxA2AURL string - authenticator auth.AuthProvider - a2aBaseOptions []a2aclient.Option + cache crcache.Cache + handlerMux A2AHandlerMux + a2aBaseURL string + sandboxA2AURL string + authenticator auth.AuthProvider } var _ manager.Runnable = (*A2ARegistrar)(nil) @@ -40,9 +39,9 @@ func NewA2ARegistrar( a2aBaseUrl string, sandboxA2ABaseURL string, authenticator auth.AuthProvider, - streamingMaxBuf int, - streamingInitialBuf int, - streamingTimeout time.Duration, + _ int, + _ int, + _ time.Duration, ) *A2ARegistrar { reg := &A2ARegistrar{ cache: cache, @@ -50,11 +49,6 @@ func NewA2ARegistrar( a2aBaseURL: a2aBaseUrl, sandboxA2AURL: sandboxA2ABaseURL, authenticator: authenticator, - a2aBaseOptions: []a2aclient.Option{ - a2aclient.WithTimeout(streamingTimeout), - a2aclient.WithBuffer(streamingInitialBuf, streamingMaxBuf), - debugOpt(), - }, } return reg @@ -161,26 +155,22 @@ func (a *A2ARegistrar) upsertAgentHandler(ctx context.Context, agent v1alpha2.Ag provider := resolveProviderName(ctx, a.cache, agent) - client, err := a2aclient.NewA2AClient( - card.URL, - append( - a.a2aBaseOptions, - a2aclient.WithHTTPReqHandler( - &traceInjectHandler{ - next: authimpl.A2ARequestHandler( - a.authenticator, - agentRef, - ), - }, - ), - )..., + httpClient := debugHTTPClient() + client, err := a2aclient.NewFromEndpoints( + ctx, + // TODO: Switch this to 1.0 in release 0.11.0 when all agents are migrated to v1 + filterInterfacesByVersion(card.SupportedInterfaces, a2atype.ProtocolVersion("0.3")), + a2aclient.WithJSONRPCTransport(httpClient), + a2aclient.WithCallInterceptors( + NewUpstreamAuthInterceptor(a.authenticator, agentRef), + ), ) if err != nil { return fmt.Errorf("create A2A client for %s: %w", agentRef, err) } cardCopy := *card - cardCopy.URL = a.a2aRouteURL(agent) + cardCopy.SupportedInterfaces = cloneInterfacesWithURL(card.SupportedInterfaces, a.a2aRouteURL(agent)) if err := a.handlerMux.SetAgentHandler(a2aRouteKey(agent), client, cardCopy, newA2ATracingMiddleware(agentRef, provider)); err != nil { return fmt.Errorf("set handler for %s: %w", agentRef, err) @@ -190,7 +180,7 @@ func (a *A2ARegistrar) upsertAgentHandler(ctx context.Context, agent v1alpha2.Ag return nil } -func debugOpt() a2aclient.Option { +func debugHTTPClient() *http.Client { debugAddr := env.KagentA2ADebugAddr.Get() if debugAddr != "" { client := new(http.Client) @@ -200,10 +190,9 @@ func debugOpt() a2aclient.Option { return zeroDialer.DialContext(ctx, network, debugAddr) }, } - return a2aclient.WithHTTPClient(client) - } else { - return func(*a2aclient.A2AClient) {} + return client } + return &http.Client{} } func (a *A2ARegistrar) a2aRouteURL(agent v1alpha2.AgentObject) string { @@ -222,3 +211,44 @@ func a2aRoutePath(agent v1alpha2.AgentObject) string { agentRef := types.NamespacedName{Namespace: agent.GetNamespace(), Name: agent.GetName()} return routeKey(agent.GetWorkloadMode() == v1alpha2.WorkloadModeSandbox, agentRef.Namespace, agentRef.Name) } + +func cloneInterfacesWithURL(interfaces []*a2atype.AgentInterface, url string) []*a2atype.AgentInterface { + if len(interfaces) == 0 { + return []*a2atype.AgentInterface{ + { + URL: url, + ProtocolBinding: a2atype.TransportProtocolJSONRPC, + ProtocolVersion: a2atype.Version, + }, + } + } + result := make([]*a2atype.AgentInterface, 0, len(interfaces)) + for _, i := range interfaces { + if i == nil { + continue + } + copied := *i + copied.URL = url + if copied.ProtocolVersion == "" { + copied.ProtocolVersion = a2atype.Version + } + result = append(result, &copied) + } + return result +} + +func filterInterfacesByVersion(interfaces []*a2atype.AgentInterface, version a2atype.ProtocolVersion) []*a2atype.AgentInterface { + filtered := make([]*a2atype.AgentInterface, 0, len(interfaces)) + for _, i := range interfaces { + if i == nil { + continue + } + if i.ProtocolVersion == version { + filtered = append(filtered, i) + } + } + if len(filtered) > 0 { + return filtered + } + return interfaces +} diff --git a/go/core/internal/a2a/a2a_utils.go b/go/core/internal/a2a/a2a_utils.go index bdd69663a8..99d90aa7d7 100644 --- a/go/core/internal/a2a/a2a_utils.go +++ b/go/core/internal/a2a/a2a_utils.go @@ -3,15 +3,18 @@ package a2a import ( "strings" - "trpc.group/trpc-go/trpc-a2a-go/protocol" + a2atype "github.com/a2aproject/a2a-go/v2/a2a" ) // ExtractText extracts the text content from a message. -func ExtractText(message protocol.Message) string { +func ExtractText(message *a2atype.Message) string { + if message == nil { + return "" + } builder := strings.Builder{} for _, part := range message.Parts { - if textPart, ok := part.(*protocol.TextPart); ok { - builder.WriteString(textPart.Text) + if part != nil { + builder.WriteString(part.Text()) } } return builder.String() diff --git a/go/core/internal/a2a/client_interceptors.go b/go/core/internal/a2a/client_interceptors.go new file mode 100644 index 0000000000..c1800d14be --- /dev/null +++ b/go/core/internal/a2a/client_interceptors.go @@ -0,0 +1,66 @@ +package a2a + +import ( + "context" + "net/http" + + "github.com/a2aproject/a2a-go/v2/a2aclient" + "github.com/kagent-dev/kagent/go/core/pkg/auth" + "go.opentelemetry.io/otel/propagation" + "k8s.io/apimachinery/pkg/types" +) + +type staticHeadersInterceptor struct { + a2aclient.PassthroughInterceptor + headers map[string]string +} + +func NewStaticHeadersInterceptor(headers map[string]string) a2aclient.CallInterceptor { + return &staticHeadersInterceptor{headers: headers} +} + +func (s *staticHeadersInterceptor) Before(ctx context.Context, req *a2aclient.Request) (context.Context, any, error) { + for k, v := range s.headers { + if v != "" { + req.ServiceParams.Append(k, v) + } + } + return ctx, nil, nil +} + +type upstreamAuthInterceptor struct { + a2aclient.PassthroughInterceptor + authProvider auth.AuthProvider + agentRef types.NamespacedName +} + +func NewUpstreamAuthInterceptor(authProvider auth.AuthProvider, agentRef types.NamespacedName) a2aclient.CallInterceptor { + return &upstreamAuthInterceptor{ + authProvider: authProvider, + agentRef: agentRef, + } +} + +func (u *upstreamAuthInterceptor) Before(ctx context.Context, req *a2aclient.Request) (context.Context, any, error) { + httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, req.BaseURL, nil) + if err != nil { + return ctx, nil, err + } + if session, ok := auth.AuthSessionFrom(ctx); ok { + upstreamPrincipal := auth.Principal{ + Agent: auth.Agent{ + ID: u.agentRef.String(), + }, + } + if err := u.authProvider.UpstreamAuth(httpReq, session, upstreamPrincipal); err != nil { + return ctx, nil, err + } + } + propagation.TraceContext{}.Inject(ctx, propagation.HeaderCarrier(httpReq.Header)) + for k, values := range httpReq.Header { + for _, value := range values { + req.ServiceParams.Append(k, value) + } + } + return ctx, nil, nil +} diff --git a/go/core/internal/a2a/manager.go b/go/core/internal/a2a/manager.go index 97141e4a0e..29d0bb86d3 100644 --- a/go/core/internal/a2a/manager.go +++ b/go/core/internal/a2a/manager.go @@ -2,58 +2,46 @@ package a2a import ( "context" + "fmt" + "iter" - "trpc.group/trpc-go/trpc-a2a-go/client" - "trpc.group/trpc-go/trpc-a2a-go/protocol" - "trpc.group/trpc-go/trpc-a2a-go/taskmanager" + a2atype "github.com/a2aproject/a2a-go/v2/a2a" + a2aclient "github.com/a2aproject/a2a-go/v2/a2aclient" + "github.com/a2aproject/a2a-go/v2/a2asrv" ) -type PassthroughManager struct { - client *client.A2AClient +type PassthroughExecutor struct { + client *a2aclient.Client } -func NewPassthroughManager(client *client.A2AClient) taskmanager.TaskManager { - return &PassthroughManager{ +func NewPassthroughExecutor(client *a2aclient.Client) a2asrv.AgentExecutor { + return &PassthroughExecutor{ client: client, } } -func (m *PassthroughManager) OnSendMessage(ctx context.Context, request protocol.SendMessageParams) (*protocol.MessageResult, error) { - if request.Message.MessageID == "" { - request.Message.MessageID = protocol.GenerateMessageID() +func (m *PassthroughExecutor) Execute(ctx context.Context, execCtx *a2asrv.ExecutorContext) iter.Seq2[a2atype.Event, error] { + return func(yield func(a2atype.Event, error) bool) { + if execCtx.Message == nil { + yield(nil, fmt.Errorf("missing message in executor context")) + return + } + req := &a2atype.SendMessageRequest{Message: execCtx.Message} + for event, err := range m.client.SendStreamingMessage(ctx, req) { + if !yield(event, err) { + return + } + } } - if request.Message.Kind == "" { - request.Message.Kind = protocol.KindMessage - } - return m.client.SendMessage(ctx, request) } -func (m *PassthroughManager) OnSendMessageStream(ctx context.Context, request protocol.SendMessageParams) (<-chan protocol.StreamingMessageEvent, error) { - if request.Message.MessageID == "" { - request.Message.MessageID = protocol.GenerateMessageID() - } - if request.Message.Kind == "" { - request.Message.Kind = protocol.KindMessage +func (m *PassthroughExecutor) Cancel(ctx context.Context, execCtx *a2asrv.ExecutorContext) iter.Seq2[a2atype.Event, error] { + return func(yield func(a2atype.Event, error) bool) { + task, err := m.client.CancelTask(ctx, &a2atype.CancelTaskRequest{ID: execCtx.TaskID}) + if err != nil { + yield(nil, err) + return + } + yield(task, nil) } - return m.client.StreamMessage(ctx, request) -} - -func (m *PassthroughManager) OnGetTask(ctx context.Context, params protocol.TaskQueryParams) (*protocol.Task, error) { - return m.client.GetTasks(ctx, params) -} - -func (m *PassthroughManager) OnCancelTask(ctx context.Context, params protocol.TaskIDParams) (*protocol.Task, error) { - return m.client.CancelTasks(ctx, params) -} - -func (m *PassthroughManager) OnPushNotificationSet(ctx context.Context, params protocol.TaskPushNotificationConfig) (*protocol.TaskPushNotificationConfig, error) { - return m.client.SetPushNotification(ctx, params) -} - -func (m *PassthroughManager) OnPushNotificationGet(ctx context.Context, params protocol.TaskIDParams) (*protocol.TaskPushNotificationConfig, error) { - return m.client.GetPushNotification(ctx, params) -} - -func (m *PassthroughManager) OnResubscribe(ctx context.Context, params protocol.TaskIDParams) (<-chan protocol.StreamingMessageEvent, error) { - return m.client.ResubscribeTask(ctx, params) } diff --git a/go/core/internal/a2a/trace.go b/go/core/internal/a2a/trace.go index bf8d7c94ad..81428a3d57 100644 --- a/go/core/internal/a2a/trace.go +++ b/go/core/internal/a2a/trace.go @@ -6,12 +6,10 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.39.0" "go.opentelemetry.io/otel/trace" "k8s.io/apimachinery/pkg/types" crcache "sigs.k8s.io/controller-runtime/pkg/cache" - a2aclient "trpc.group/trpc-go/trpc-a2a-go/client" "github.com/kagent-dev/kagent/go/api/v1alpha2" ) @@ -45,18 +43,6 @@ func (m *a2aTracingMiddleware) Wrap(next http.Handler) http.Handler { }) } -// traceInjectHandler wraps an HTTPReqHandler and injects W3C TraceContext -// headers (traceparent, tracestate) from the Go context into every outgoing -// proxy request, so the downstream agent receives the active span as its parent. -type traceInjectHandler struct { - next a2aclient.HTTPReqHandler -} - -func (h *traceInjectHandler) Handle(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) { - propagation.TraceContext{}.Inject(ctx, propagation.HeaderCarrier(req.Header)) - return h.next.Handle(ctx, client, req) -} - // resolveProviderName looks up the ModelConfig for a declarative agent and // returns the corresponding gen_ai.provider.name attribute. Falls back to "kagent" // for BYO agents or if the ModelConfig cannot be fetched. diff --git a/go/core/internal/a2a/trace_test.go b/go/core/internal/a2a/trace_test.go index a7ee45c482..3e7c8de698 100644 --- a/go/core/internal/a2a/trace_test.go +++ b/go/core/internal/a2a/trace_test.go @@ -7,68 +7,12 @@ import ( "testing" "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/propagation" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" semconv "go.opentelemetry.io/otel/semconv/v1.39.0" - "go.opentelemetry.io/otel/trace" "k8s.io/apimachinery/pkg/types" ) -// mockHTTPReqHandler captures the request passed to Handle for inspection. -type mockHTTPReqHandler struct { - capturedReq *http.Request -} - -func (m *mockHTTPReqHandler) Handle(_ context.Context, _ *http.Client, req *http.Request) (*http.Response, error) { - m.capturedReq = req - return &http.Response{StatusCode: http.StatusOK}, nil -} - -func TestTraceInjectHandler_InjectsHeader(t *testing.T) { - const rawTraceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" - - ctx := propagation.TraceContext{}.Extract( - context.Background(), - propagation.MapCarrier{"traceparent": rawTraceparent}, - ) - - mock := &mockHTTPReqHandler{} - h := &traceInjectHandler{next: mock} - - req := httptest.NewRequest(http.MethodPost, "/", nil) - if _, err := h.Handle(ctx, nil, req); err != nil { - t.Fatalf("unexpected error: %v", err) - } - - got := mock.capturedReq.Header.Get("traceparent") - if got == "" { - t.Fatal("expected traceparent header on outgoing request, got none") - } - - // The injected header must carry the same trace ID as the incoming context. - outCtx := propagation.TraceContext{}.Extract(context.Background(), propagation.HeaderCarrier(mock.capturedReq.Header)) - wantTraceID := trace.SpanContextFromContext(ctx).TraceID() - gotTraceID := trace.SpanContextFromContext(outCtx).TraceID() - if wantTraceID != gotTraceID { - t.Errorf("trace ID: want %s, got %s", wantTraceID, gotTraceID) - } -} - -func TestTraceInjectHandler_NoHeaderWhenNoTrace(t *testing.T) { - mock := &mockHTTPReqHandler{} - h := &traceInjectHandler{next: mock} - - req := httptest.NewRequest(http.MethodPost, "/", nil) - if _, err := h.Handle(context.Background(), nil, req); err != nil { - t.Fatalf("unexpected error: %v", err) - } - - if got := mock.capturedReq.Header.Get("traceparent"); got != "" { - t.Errorf("expected no traceparent header, got %q", got) - } -} - func TestA2ATracingMiddleware_SetsGenAIAttributes(t *testing.T) { exporter := tracetest.NewInMemoryExporter() tp := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter)) diff --git a/go/core/internal/controller/translator/agent/compiler.go b/go/core/internal/controller/translator/agent/compiler.go index 0232a859e6..c0f22eee8b 100644 --- a/go/core/internal/controller/translator/agent/compiler.go +++ b/go/core/internal/controller/translator/agent/compiler.go @@ -5,10 +5,10 @@ import ( "fmt" "slices" + a2a "github.com/a2aproject/a2a-go/v2/a2a" "github.com/kagent-dev/kagent/go/api/adk" "github.com/kagent-dev/kagent/go/api/v1alpha2" "github.com/kagent-dev/kagent/go/core/internal/utils" - "trpc.group/trpc-go/trpc-a2a-go/server" ) // AgentManifestInputs holds the translated data needed to emit Kubernetes resources. @@ -16,7 +16,7 @@ type AgentManifestInputs struct { Config *adk.AgentConfig Sandbox *v1alpha2.SandboxConfig Deployment *resolvedDeployment - AgentCard *server.AgentCard + AgentCard *a2a.AgentCard SecretHashBytes []byte } diff --git a/go/core/internal/controller/translator/agent/manifest_builder.go b/go/core/internal/controller/translator/agent/manifest_builder.go index 3b059bbef5..005c397737 100644 --- a/go/core/internal/controller/translator/agent/manifest_builder.go +++ b/go/core/internal/controller/translator/agent/manifest_builder.go @@ -6,6 +6,7 @@ import ( "fmt" "maps" + a2a "github.com/a2aproject/a2a-go/v2/a2a" "github.com/kagent-dev/kagent/go/api/adk" "github.com/kagent-dev/kagent/go/api/v1alpha2" "github.com/kagent-dev/kagent/go/core/internal/controller/translator/labels" @@ -18,7 +19,6 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "trpc.group/trpc-go/trpc-a2a-go/server" ) type manifestContext struct { @@ -128,7 +128,7 @@ func (a *adkApiTranslator) buildConfigSecret( manifestCtx manifestContext, cfg *adk.AgentConfig, sandboxCfg *v1alpha2.SandboxConfig, - card *server.AgentCard, + card *a2a.AgentCard, modelConfigSecretHashBytes []byte, ) (*configSecretInputs, error) { cfgJSON := "" diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_a2a_config.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_a2a_config.json index 3a467bf506..bc4a1117f7 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_a2a_config.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_a2a_config.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,7 +11,6 @@ ], "description": "", "name": "a2a_agent", - "preferredTransport": "JSONRPC", "skills": [ { "description": "Summarizes text", @@ -21,7 +18,18 @@ "tags": null } ], - "url": "http://a2a-agent.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://a2a-agent.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://a2a-agent.test:8080" + } + ], "version": "" }, "config": { @@ -60,7 +68,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"a2a_agent\",\"description\":\"\",\"url\":\"http://a2a-agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[{\"id\":\"summarize\",\"name\":\"Summarize\",\"description\":\"Summarizes text\",\"tags\":null}],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://a2a-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://a2a-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"a2a_agent\",\"skills\":[{\"description\":\"Summarizes text\",\"id\":\"summarize\",\"name\":\"Summarize\",\"tags\":null}],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}" } }, @@ -130,7 +138,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "16405455094195710426" + "kagent.dev/config-hash": "9207770362976028198" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_allowed_headers.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_allowed_headers.json index 6c94d2f86d..5d300ce603 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_allowed_headers.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_allowed_headers.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent.test:8080" + } + ], "version": "" }, "config": { @@ -70,7 +78,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent\",\"description\":\"\",\"url\":\"http://agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"http_tools\":[{\"params\":{\"url\":\"http://mcp-server.test:8080/mcp\",\"headers\":{}},\"tools\":[\"tool1\",\"tool2\"],\"allowed_headers\":[\"x-user-email\",\"x-tenant-id\"]}],\"stream\":false}" } }, @@ -140,7 +148,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "5387783918115122395" + "kagent.dev/config-hash": "2138527890147516382" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_code.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_code.json index 597478474b..57690d3946 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_code.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_code.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent_with_code", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-with-code.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-with-code.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-with-code.test:8080" + } + ], "version": "" }, "config": { @@ -62,7 +70,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_with_code\",\"description\":\"\",\"url\":\"http://agent-with-code.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-code.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-code.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_code\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"reasoning_effort\":\"low\",\"temperature\":0.7,\"top_p\":0.95},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"execute_code\":true,\"stream\":false}", "srt-settings.json": "{\"filesystem\":{\"allowWrite\":[\".\",\"/tmp\"],\"denyRead\":[],\"denyWrite\":[]},\"network\":{\"allowedDomains\":[],\"deniedDomains\":[]}}" } @@ -133,7 +141,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "17724008177186270332" + "kagent.dev/config-hash": "15600117866655840721" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_context_config.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_context_config.json index 1ac7356e6d..d2a8cc6780 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_context_config.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_context_config.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "Agent with context management", "name": "agent_with_context", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-with-context.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-with-context.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-with-context.test:8080" + } + ], "version": "" }, "config": { @@ -72,7 +80,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_with_context\",\"description\":\"Agent with context management\",\"url\":\"http://agent-with-context.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-context.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-context.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"Agent with context management\",\"name\":\"agent_with_context\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"temperature\":0.7},\"description\":\"Agent with context management\",\"instruction\":\"You are a helpful assistant with context management enabled.\",\"stream\":false,\"context_config\":{\"compaction\":{\"compaction_interval\":5,\"overlap_size\":2,\"summarizer_model\":{\"type\":\"anthropic\",\"model\":\"claude-3-haiku\"},\"prompt_template\":\"Summarize the following conversation events concisely.\",\"token_threshold\":50000,\"event_retention_size\":10}}}" } }, @@ -142,7 +150,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "16891892632874106599" + "kagent.dev/config-hash": "16190589400428186426" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_cross_namespace_tools.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_cross_namespace_tools.json index edaedd9719..96cba3c405 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_cross_namespace_tools.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_cross_namespace_tools.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "An agent that uses cross-namespace tools", "name": "source_agent", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://source-agent.source-ns:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://source-agent.source-ns:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://source-agent.source-ns:8080" + } + ], "version": "" }, "config": { @@ -76,7 +84,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"source_agent\",\"description\":\"An agent that uses cross-namespace tools\",\"url\":\"http://source-agent.source-ns:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://source-agent.source-ns:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://source-agent.source-ns:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"An agent that uses cross-namespace tools\",\"name\":\"source_agent\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"An agent that uses cross-namespace tools\",\"instruction\":\"You are an assistant with access to shared tools.\",\"http_tools\":[{\"params\":{\"url\":\"http://tools.tools-ns.svc:8080/mcp\",\"headers\":{\"Authorization\":\"tool-secret-token\"},\"timeout\":30},\"tools\":[\"list_resources\",\"get_resource\"]}],\"remote_agents\":[{\"name\":\"tools_ns__NS__tools_agent\",\"url\":\"http://tools-agent.tools-ns:8080\",\"description\":\"An agent that can be used as a cross-namespace tool\"}],\"stream\":false}" } }, @@ -146,7 +154,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "6527476808794662414" + "kagent.dev/config-hash": "9411017991045658659" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_custom_sa.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_custom_sa.json index d2ecefd3a6..5bd3760274 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_custom_sa.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_custom_sa.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent_with_custom_sa", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-with-custom-sa.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-with-custom-sa.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-with-custom-sa.test:8080" + } + ], "version": "" }, "config": { @@ -54,7 +62,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_with_custom_sa\",\"description\":\"\",\"url\":\"http://agent-with-custom-sa.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-custom-sa.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-custom-sa.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_custom_sa\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}" } }, @@ -99,7 +107,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "13887560842969010876" + "kagent.dev/config-hash": "11401466948750956923" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_default_sa.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_default_sa.json index 19548c5d01..595bfbf6ec 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_default_sa.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_default_sa.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent_with_default_sa", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-with-default-sa.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-with-default-sa.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-with-default-sa.test:8080" + } + ], "version": "" }, "config": { @@ -54,7 +62,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_with_default_sa\",\"description\":\"\",\"url\":\"http://agent-with-default-sa.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-default-sa.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-default-sa.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_default_sa\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}" } }, @@ -99,7 +107,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "8931708887213057908" + "kagent.dev/config-hash": "1706358953235401870" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_embedding_provider.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_embedding_provider.json index f2a9de95bf..243e517582 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_embedding_provider.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_embedding_provider.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent_cross_provider_memory", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-cross-provider-memory.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-cross-provider-memory.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-cross-provider-memory.test:8080" + } + ], "version": "" }, "config": { @@ -60,7 +68,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_cross_provider_memory\",\"description\":\"\",\"url\":\"http://agent-cross-provider-memory.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-cross-provider-memory.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-cross-provider-memory.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_cross_provider_memory\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are an assistant.\",\"stream\":false,\"memory\":{\"embedding\":{\"provider\":\"gemini_vertex_ai\",\"model\":\"text-embedding-005\"}}}" } }, @@ -130,7 +138,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "10448813021284432574" + "kagent.dev/config-hash": "12134627225846032635" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_extra_containers.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_extra_containers.json index 6f9d1fa0bf..b2e78f8c4f 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_extra_containers.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_extra_containers.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent_with_extra_containers", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-with-extra-containers.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-with-extra-containers.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-with-extra-containers.test:8080" + } + ], "version": "" }, "config": { @@ -54,7 +62,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_with_extra_containers\",\"description\":\"\",\"url\":\"http://agent-with-extra-containers.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-extra-containers.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-extra-containers.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_extra_containers\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}" } }, @@ -124,7 +132,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "6067225395749761191" + "kagent.dev/config-hash": "5464807304477718486" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_git_skills.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_git_skills.json index 98cc97beab..f1f27f8053 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_git_skills.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_git_skills.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "git_skills_agent", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://git-skills-agent.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://git-skills-agent.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://git-skills-agent.test:8080" + } + ], "version": "" }, "config": { @@ -61,7 +69,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"git_skills_agent\",\"description\":\"\",\"url\":\"http://git-skills-agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://git-skills-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://git-skills-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"git_skills_agent\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"reasoning_effort\":\"low\",\"temperature\":0.7,\"top_p\":0.95},\"description\":\"\",\"instruction\":\"You are a helpful assistant with skills from git.\",\"stream\":false}", "srt-settings.json": "{\"filesystem\":{\"allowWrite\":[\".\",\"/tmp\"],\"denyRead\":[],\"denyWrite\":[]},\"network\":{\"allowedDomains\":[],\"deniedDomains\":[]}}" } @@ -132,7 +140,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "9443054578640766875" + "kagent.dev/config-hash": "18122633214966413236" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_http_toolserver.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_http_toolserver.json index 838ef2dbc2..4e3e475fae 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_http_toolserver.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_http_toolserver.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent.test:8080" + } + ], "version": "" }, "config": { @@ -69,7 +77,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent\",\"description\":\"\",\"url\":\"http://agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a math toolserver. Focus on solving mathematical problems step by step.\",\"http_tools\":[{\"params\":{\"url\":\"http://localhost:8084/mcp\",\"headers\":{\"MATH\":\"sk-test-api-key\"},\"timeout\":30,\"sse_read_timeout\":300},\"tools\":[\"k8s_get_resources\"]}],\"stream\":false}" } }, @@ -139,7 +147,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "4534869863837852487" + "kagent.dev/config-hash": "8395429179909149115" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_mcp_service.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_mcp_service.json index 852ebeeafd..56c521d3f1 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_mcp_service.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_mcp_service.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent.test:8080" + } + ], "version": "" }, "config": { @@ -65,7 +73,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent\",\"description\":\"\",\"url\":\"http://agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a math toolserver. Focus on solving mathematical problems step by step.\",\"http_tools\":[{\"params\":{\"url\":\"http://toolserver.test:8084/mcp\",\"headers\":{}},\"tools\":[\"k8s_get_resources\"]}],\"stream\":false}" } }, @@ -135,7 +143,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "12595169530626754046" + "kagent.dev/config-hash": "17883346513180388434" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_memory.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_memory.json index c8b70f08b4..97d6a583e3 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_memory.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_memory.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent_with_memory", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-with-memory.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-with-memory.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-with-memory.test:8080" + } + ], "version": "" }, "config": { @@ -65,7 +73,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_with_memory\",\"description\":\"\",\"url\":\"http://agent-with-memory.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-memory.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-memory.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_memory\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"temperature\":0.7},\"description\":\"\",\"instruction\":\"You are a helpful assistant with memory. Save important findings and use past context when relevant.\",\"stream\":false,\"memory\":{\"embedding\":{\"provider\":\"openai\",\"model\":\"text-embedding-3-small\"}}}" } }, @@ -135,7 +143,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "11340664467578377382" + "kagent.dev/config-hash": "2292760194225518181" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_nested_agent.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_nested_agent.json index caba32bec9..9f8484bdb7 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_nested_agent.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_nested_agent.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "parent_agent", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://parent-agent.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://parent-agent.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://parent-agent.test:8080" + } + ], "version": "" }, "config": { @@ -63,7 +71,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"parent_agent\",\"description\":\"\",\"url\":\"http://parent-agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://parent-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://parent-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"parent_agent\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a coordinating agent that can delegate tasks to specialists.\",\"remote_agents\":[{\"name\":\"test__NS__specialist_agent\",\"url\":\"http://specialist-agent.test:8080\",\"headers\":{\"FOO\":\"sup3rs3cr3t\"}}],\"stream\":false}" } }, @@ -133,7 +141,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "963887663212034528" + "kagent.dev/config-hash": "5732257866142725806" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_passthrough.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_passthrough.json index 0a9a7fcda8..48ca6f511f 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_passthrough.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_passthrough.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "passthrough_agent", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://passthrough-agent.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://passthrough-agent.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://passthrough-agent.test:8080" + } + ], "version": "" }, "config": { @@ -56,7 +64,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"passthrough_agent\",\"description\":\"\",\"url\":\"http://passthrough-agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://passthrough-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://passthrough-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"passthrough_agent\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"api_key_passthrough\":true,\"base_url\":\"\",\"temperature\":0.7},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}" } }, @@ -126,7 +134,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "2436412068147442351" + "kagent.dev/config-hash": "15783211955608997214" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_prompt_template.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_prompt_template.json index e13ba958fe..4ce449cef0 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_prompt_template.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_prompt_template.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "A Kubernetes troubleshooting agent", "name": "agent_with_prompt_template", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-with-prompt-template.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-with-prompt-template.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-with-prompt-template.test:8080" + } + ], "version": "" }, "config": { @@ -67,7 +75,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_with_prompt_template\",\"description\":\"A Kubernetes troubleshooting agent\",\"url\":\"http://agent-with-prompt-template.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-prompt-template.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-prompt-template.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"A Kubernetes troubleshooting agent\",\"name\":\"agent_with_prompt_template\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"A Kubernetes troubleshooting agent\",\"instruction\":\"## Preamble\\nYou are a helpful Kubernetes assistant.\\n\\n\\nYou are agent-with-prompt-template, operating in test.\\nYour purpose: A Kubernetes troubleshooting agent\\n\\nAvailable tools: k8s_get_resources, k8s_describe_resource, \\n\\n## Safety Guidelines\\nNever delete resources without explicit user confirmation.\\n\\n\",\"http_tools\":[{\"params\":{\"url\":\"http://localhost:8084/mcp\",\"headers\":{},\"timeout\":30},\"tools\":[\"k8s_get_resources\",\"k8s_describe_resource\"]}],\"stream\":false}" } }, @@ -137,7 +145,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "17922600608856796760" + "kagent.dev/config-hash": "1641050374260693601" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy.json index 901ce90339..27db580483 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent_with_proxy", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-with-proxy.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-with-proxy.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-with-proxy.test:8080" + } + ], "version": "" }, "config": { @@ -76,7 +84,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_with_proxy\",\"description\":\"\",\"url\":\"http://agent-with-proxy.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-proxy.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-proxy.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_proxy\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are an agent that uses proxies.\",\"http_tools\":[{\"params\":{\"url\":\"http://proxy.kagent.svc.cluster.local:8080/mcp\",\"headers\":{\"x-kagent-host\":\"test-mcp-server.kagent\"}},\"tools\":[\"test-tool\"]}],\"remote_agents\":[{\"name\":\"test__NS__nested_agent\",\"url\":\"http://proxy.kagent.svc.cluster.local:8080\",\"headers\":{\"x-kagent-host\":\"nested-agent.test\"}}],\"stream\":false}" } }, @@ -146,7 +154,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "1327907664326337797" + "kagent.dev/config-hash": "9229675023087394140" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_external_remotemcp.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_external_remotemcp.json index 53cf7f4bc7..cecccab26a 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_external_remotemcp.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_external_remotemcp.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent_with_proxy_external", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-with-proxy-external.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-with-proxy-external.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-with-proxy-external.test:8080" + } + ], "version": "" }, "config": { @@ -65,7 +73,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_with_proxy_external\",\"description\":\"\",\"url\":\"http://agent-with-proxy-external.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-proxy-external.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-proxy-external.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_proxy_external\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are an agent that uses proxies.\",\"http_tools\":[{\"params\":{\"url\":\"https://external-mcp.example.com/mcp\",\"headers\":{}},\"tools\":[\"test-tool\"]}],\"stream\":false}" } }, @@ -135,7 +143,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "8764617675044151097" + "kagent.dev/config-hash": "15837079965376691471" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver.json index e80a0bf78e..c15712741f 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent_with_proxy_mcpserver", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-with-proxy-mcpserver.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-with-proxy-mcpserver.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-with-proxy-mcpserver.test:8080" + } + ], "version": "" }, "config": { @@ -68,7 +76,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_with_proxy_mcpserver\",\"description\":\"\",\"url\":\"http://agent-with-proxy-mcpserver.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-proxy-mcpserver.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-proxy-mcpserver.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_proxy_mcpserver\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are an agent that uses proxies.\",\"http_tools\":[{\"params\":{\"url\":\"http://proxy.kagent.svc.cluster.local:8080/mcp\",\"headers\":{\"x-kagent-host\":\"test-mcp-server.test\"},\"timeout\":30},\"tools\":[\"test-tool\"]}],\"stream\":false}" } }, @@ -138,7 +146,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "17237679247457206561" + "kagent.dev/config-hash": "12725512234622067294" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver_custom_timeout.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver_custom_timeout.json index 150bc253d0..cb42efabec 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver_custom_timeout.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver_custom_timeout.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent_with_proxy_mcpserver_timeout", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-with-proxy-mcpserver-timeout.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-with-proxy-mcpserver-timeout.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-with-proxy-mcpserver-timeout.test:8080" + } + ], "version": "" }, "config": { @@ -68,7 +76,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_with_proxy_mcpserver_timeout\",\"description\":\"\",\"url\":\"http://agent-with-proxy-mcpserver-timeout.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-proxy-mcpserver-timeout.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-proxy-mcpserver-timeout.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_proxy_mcpserver_timeout\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are an agent that uses proxies.\",\"http_tools\":[{\"params\":{\"url\":\"http://proxy.kagent.svc.cluster.local:8080/mcp\",\"headers\":{\"x-kagent-host\":\"test-mcp-server.test\"},\"timeout\":60},\"tools\":[\"test-tool\"]}],\"stream\":false}" } }, @@ -138,7 +146,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "7011830610500130414" + "kagent.dev/config-hash": "8496090108219408384" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_service.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_service.json index 0010bc16d9..ad72b12953 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_service.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_service.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent_with_proxy_service", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-with-proxy-service.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-with-proxy-service.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-with-proxy-service.test:8080" + } + ], "version": "" }, "config": { @@ -67,7 +75,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_with_proxy_service\",\"description\":\"\",\"url\":\"http://agent-with-proxy-service.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-proxy-service.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-proxy-service.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_proxy_service\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are an agent that uses proxies.\",\"http_tools\":[{\"params\":{\"url\":\"http://proxy.kagent.svc.cluster.local:8080/mcp\",\"headers\":{\"x-kagent-host\":\"toolserver.test\"}},\"tools\":[\"k8s_get_resources\"]}],\"stream\":false}" } }, @@ -137,7 +145,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "17657014285076291438" + "kagent.dev/config-hash": "4422521160223291204" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_require_approval.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_require_approval.json index e48937b0f2..09162b02d5 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_require_approval.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_require_approval.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent.test:8080" + } + ], "version": "" }, "config": { @@ -71,7 +79,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent\",\"description\":\"\",\"url\":\"http://agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You help users manage files.\",\"http_tools\":[{\"params\":{\"url\":\"http://toolserver.test:8084/mcp\",\"headers\":{}},\"tools\":[\"read_file\",\"write_file\",\"delete_file\"],\"require_approval\":[\"delete_file\",\"write_file\"]}],\"stream\":false}" } }, @@ -141,7 +149,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "2233830583481326333" + "kagent.dev/config-hash": "9815976453219091618" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_scheduling_attributes.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_scheduling_attributes.json index c1f32a0921..87b51ff4b7 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_scheduling_attributes.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_scheduling_attributes.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent_with_scheduling_attributes", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-with-scheduling-attributes.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-with-scheduling-attributes.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-with-scheduling-attributes.test:8080" + } + ], "version": "" }, "config": { @@ -61,7 +69,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_with_scheduling_attributes\",\"description\":\"\",\"url\":\"http://agent-with-scheduling-attributes.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-scheduling-attributes.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-scheduling-attributes.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_scheduling_attributes\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"reasoning_effort\":\"low\",\"temperature\":0.7,\"top_p\":0.95},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}" } }, @@ -131,7 +139,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "7052837506672819306" + "kagent.dev/config-hash": "14381130188719579868" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_security_context.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_security_context.json index b1372c0b3b..2785e6af5b 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_security_context.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_security_context.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent_with_security_context", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-with-security-context.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-with-security-context.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-with-security-context.test:8080" + } + ], "version": "" }, "config": { @@ -61,7 +69,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_with_security_context\",\"description\":\"\",\"url\":\"http://agent-with-security-context.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-security-context.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-security-context.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_security_context\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"reasoning_effort\":\"low\",\"temperature\":0.7,\"top_p\":0.95},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}" } }, @@ -131,7 +139,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "3665823864130830967" + "kagent.dev/config-hash": "9168209007673468962" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_skills.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_skills.json index e65fa19d57..60d60b1ef0 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_skills.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_skills.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "skills_agent", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://skills-agent.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://skills-agent.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://skills-agent.test:8080" + } + ], "version": "" }, "config": { @@ -61,7 +69,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"skills_agent\",\"description\":\"\",\"url\":\"http://skills-agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://skills-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://skills-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"skills_agent\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"reasoning_effort\":\"low\",\"temperature\":0.7,\"top_p\":0.95},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}", "srt-settings.json": "{\"filesystem\":{\"allowWrite\":[\".\",\"/tmp\"],\"denyRead\":[],\"denyWrite\":[]},\"network\":{\"allowedDomains\":[],\"deniedDomains\":[]}}" } @@ -132,7 +140,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "4496754913718271849" + "kagent.dev/config-hash": "3788882410372515592" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_streaming.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_streaming.json index e0d08cd124..f49f50d0ad 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_streaming.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_streaming.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "basic_agent", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://basic-agent.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://basic-agent.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://basic-agent.test:8080" + } + ], "version": "" }, "config": { @@ -61,7 +69,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"basic_agent\",\"description\":\"\",\"url\":\"http://basic-agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://basic-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://basic-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"basic_agent\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"reasoning_effort\":\"low\",\"temperature\":0.7,\"top_p\":0.95},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":true}" } }, @@ -131,7 +139,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "4834502221619930746" + "kagent.dev/config-hash": "739336962982548496" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_configmap.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_configmap.json index de6ed10545..f58a025694 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_configmap.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_configmap.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent_with_configmap_system_message", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-with-configmap-system-message.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-with-configmap-system-message.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-with-configmap-system-message.test:8080" + } + ], "version": "" }, "config": { @@ -54,7 +62,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_with_configmap_system_message\",\"description\":\"\",\"url\":\"http://agent-with-configmap-system-message.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-configmap-system-message.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-configmap-system-message.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_configmap_system_message\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"Speak in the style of Shakespeare.\",\"stream\":false}" } }, @@ -124,7 +132,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "6732733949178246074" + "kagent.dev/config-hash": "9080902236721123646" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_secret.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_secret.json index a03bbbea85..61ca9af37c 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_secret.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_secret.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "agent_with_secret_system_message", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://agent-with-secret-system-message.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://agent-with-secret-system-message.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://agent-with-secret-system-message.test:8080" + } + ], "version": "" }, "config": { @@ -54,7 +62,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"agent_with_secret_system_message\",\"description\":\"\",\"url\":\"http://agent-with-secret-system-message.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-secret-system-message.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-secret-system-message.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_secret_system_message\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You will speak in the style of Shakespeare.\\n\",\"stream\":false}" } }, @@ -124,7 +132,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "1798322143389478148" + "kagent.dev/config-hash": "18027380751492011100" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/anthropic_agent.json b/go/core/internal/controller/translator/agent/testdata/outputs/anthropic_agent.json index b17be6ab43..106fa7ebed 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/anthropic_agent.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/anthropic_agent.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "anthropic_agent", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://anthropic-agent.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://anthropic-agent.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://anthropic-agent.test:8080" + } + ], "version": "" }, "config": { @@ -53,7 +61,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"anthropic_agent\",\"description\":\"\",\"url\":\"http://anthropic-agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://anthropic-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://anthropic-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"anthropic_agent\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"anthropic\",\"model\":\"claude-3-sonnet-20240229\"},\"description\":\"\",\"instruction\":\"You are Claude, an AI assistant created by Anthropic.\",\"stream\":false}" } }, @@ -123,7 +131,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "12361184581110359614" + "kagent.dev/config-hash": "8555039268477979079" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/basic_agent.json b/go/core/internal/controller/translator/agent/testdata/outputs/basic_agent.json index 81915807b9..11def185e9 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/basic_agent.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/basic_agent.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "basic_agent", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://basic-agent.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://basic-agent.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://basic-agent.test:8080" + } + ], "version": "" }, "config": { @@ -61,7 +69,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"basic_agent\",\"description\":\"\",\"url\":\"http://basic-agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://basic-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://basic-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"basic_agent\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"reasoning_effort\":\"low\",\"temperature\":0.7,\"top_p\":0.95},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}" } }, @@ -131,7 +139,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "7118705820066737230" + "kagent.dev/config-hash": "17570186208944924589" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/bedrock_agent.json b/go/core/internal/controller/translator/agent/testdata/outputs/bedrock_agent.json index c610f58ca2..9c8652b8f2 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/bedrock_agent.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/bedrock_agent.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "bedrock_agent", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://bedrock-agent.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://bedrock-agent.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://bedrock-agent.test:8080" + } + ], "version": "" }, "config": { @@ -54,7 +62,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"bedrock_agent\",\"description\":\"\",\"url\":\"http://bedrock-agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://bedrock-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://bedrock-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"bedrock_agent\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"bedrock\",\"model\":\"us.anthropic.claude-sonnet-4-20250514-v1:0\",\"region\":\"us-east-1\"},\"description\":\"\",\"instruction\":\"You are a helpful AI assistant running on AWS Bedrock.\",\"stream\":false}" } }, @@ -124,7 +132,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "11640375396581880325" + "kagent.dev/config-hash": "680468057177931757" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/ollama_agent.json b/go/core/internal/controller/translator/agent/testdata/outputs/ollama_agent.json index 02a0ddae91..5943be5774 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/ollama_agent.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/ollama_agent.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "ollama_agent", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://ollama-agent.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://ollama-agent.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://ollama-agent.test:8080" + } + ], "version": "" }, "config": { @@ -61,7 +69,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"ollama_agent\",\"description\":\"\",\"url\":\"http://ollama-agent.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://ollama-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://ollama-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"ollama_agent\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"ollama\",\"model\":\"llama3.2:latest\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"options\":{\"num_ctx\":\"2048\",\"temperature\":\"0.8\",\"top_p\":\"0.9\"}},\"description\":\"\",\"instruction\":\"You are a helpful AI assistant running locally via Ollama.\",\"stream\":false}" } }, @@ -131,7 +139,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "1298797653455180865" + "kagent.dev/config-hash": "11557155459231859304" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-custom-ca.json b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-custom-ca.json index 62e996c083..e98c0fd680 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-custom-ca.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-custom-ca.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "tls_agent_with_custom_ca", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://tls-agent-with-custom-ca.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://tls-agent-with-custom-ca.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://tls-agent-with-custom-ca.test:8080" + } + ], "version": "" }, "config": { @@ -59,7 +67,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"tls_agent_with_custom_ca\",\"description\":\"\",\"url\":\"http://tls-agent-with-custom-ca.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://tls-agent-with-custom-ca.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://tls-agent-with-custom-ca.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"tls_agent_with_custom_ca\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"tls_insecure_skip_verify\":false,\"tls_ca_cert_path\":\"/etc/ssl/certs/custom/ca.crt\",\"tls_disable_system_cas\":false,\"base_url\":\"https://internal-litellm.company.com\",\"max_tokens\":1024,\"temperature\":0.7},\"description\":\"\",\"instruction\":\"You are a helpful assistant with custom CA support.\",\"stream\":false}" } }, @@ -129,7 +137,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "7617586729040135348" + "kagent.dev/config-hash": "15757998895553750808" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-disabled-verify.json b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-disabled-verify.json index 5da58bfdcd..932b421f64 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-disabled-verify.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-disabled-verify.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "tls_agent_with_disabled_verify", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://tls-agent-with-disabled-verify.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://tls-agent-with-disabled-verify.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://tls-agent-with-disabled-verify.test:8080" + } + ], "version": "" }, "config": { @@ -58,7 +66,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"tls_agent_with_disabled_verify\",\"description\":\"\",\"url\":\"http://tls-agent-with-disabled-verify.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://tls-agent-with-disabled-verify.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://tls-agent-with-disabled-verify.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"tls_agent_with_disabled_verify\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"tls_insecure_skip_verify\":true,\"tls_disable_system_cas\":false,\"base_url\":\"https://dev-litellm.local\",\"max_tokens\":1024,\"temperature\":0.7},\"description\":\"\",\"instruction\":\"You are a helpful assistant in a development environment.\",\"stream\":false}" } }, @@ -128,7 +136,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "11053867675743064086" + "kagent.dev/config-hash": "7733456931475125811" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-system-cas-disabled.json b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-system-cas-disabled.json index 620c29c9f5..e5f88365f0 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-system-cas-disabled.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-system-cas-disabled.json @@ -1,8 +1,6 @@ { "agentCard": { "capabilities": { - "pushNotifications": false, - "stateTransitionHistory": true, "streaming": true }, "defaultInputModes": [ @@ -13,9 +11,19 @@ ], "description": "", "name": "tls_agent_with_system_cas_disabled", - "preferredTransport": "JSONRPC", "skills": null, - "url": "http://tls-agent-with-system-cas-disabled.test:8080", + "supportedInterfaces": [ + { + "protocolBinding": "JSONRPC", + "protocolVersion": "0.3", + "url": "http://tls-agent-with-system-cas-disabled.test:8080" + }, + { + "protocolBinding": "JSONRPC", + "protocolVersion": "1.0", + "url": "http://tls-agent-with-system-cas-disabled.test:8080" + } + ], "version": "" }, "config": { @@ -59,7 +67,7 @@ ] }, "stringData": { - "agent-card.json": "{\"name\":\"tls_agent_with_system_cas_disabled\",\"description\":\"\",\"url\":\"http://tls-agent-with-system-cas-disabled.test:8080\",\"version\":\"\",\"capabilities\":{\"streaming\":true,\"pushNotifications\":false,\"stateTransitionHistory\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"skills\":[],\"preferredTransport\":\"JSONRPC\"}", + "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://tls-agent-with-system-cas-disabled.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://tls-agent-with-system-cas-disabled.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"tls_agent_with_system_cas_disabled\",\"skills\":[],\"version\":\"\"}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"tls_insecure_skip_verify\":false,\"tls_ca_cert_path\":\"/etc/ssl/certs/custom/ca.crt\",\"tls_disable_system_cas\":true,\"base_url\":\"https://corp-llm-gateway.internal\",\"max_tokens\":1024,\"temperature\":0.7},\"description\":\"\",\"instruction\":\"You are a helpful assistant in a corporate environment.\",\"stream\":false}" } }, @@ -129,7 +137,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "1527591075664209989" + "kagent.dev/config-hash": "16161564609559128349" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/utils.go b/go/core/internal/controller/translator/agent/utils.go index 83b9bbd680..e633cc031d 100644 --- a/go/core/internal/controller/translator/agent/utils.go +++ b/go/core/internal/controller/translator/agent/utils.go @@ -2,38 +2,58 @@ package agent import ( "fmt" - "slices" "strings" - a2atype "github.com/a2aproject/a2a-go/a2a" + a2atype "github.com/a2aproject/a2a-go/v2/a2a" "github.com/kagent-dev/kagent/go/api/v1alpha2" - "github.com/kagent-dev/kagent/go/core/internal/utils" - "trpc.group/trpc-go/trpc-a2a-go/server" ) -func GetA2AAgentCard(agent v1alpha2.AgentObject) *server.AgentCard { +func GetA2AAgentCard(agent v1alpha2.AgentObject) *a2atype.AgentCard { spec := agent.GetAgentSpec() - preferredTransport := string(a2atype.TransportProtocolJSONRPC) - card := server.AgentCard{ - Name: strings.ReplaceAll(agent.GetName(), "-", "_"), - Description: spec.Description, - URL: fmt.Sprintf("http://%s.%s:8080", agent.GetName(), agent.GetNamespace()), - PreferredTransport: &preferredTransport, - Capabilities: server.AgentCapabilities{ - Streaming: new(true), - PushNotifications: new(false), - StateTransitionHistory: new(true), + card := a2atype.AgentCard{ + Name: strings.ReplaceAll(agent.GetName(), "-", "_"), + Description: spec.Description, + SupportedInterfaces: []*a2atype.AgentInterface{ + { + URL: fmt.Sprintf("http://%s.%s:8080", agent.GetName(), agent.GetNamespace()), + ProtocolBinding: a2atype.TransportProtocolJSONRPC, + ProtocolVersion: a2atype.ProtocolVersion("0.3"), + }, + { + URL: fmt.Sprintf("http://%s.%s:8080", agent.GetName(), agent.GetNamespace()), + ProtocolBinding: a2atype.TransportProtocolJSONRPC, + ProtocolVersion: a2atype.Version, + }, }, - // Can't be null for Python, so set to empty list - Skills: []server.AgentSkill{}, + Capabilities: a2atype.AgentCapabilities{ + Streaming: true, + PushNotifications: false, + }, + // Can't be null for Python, so set to empty list. + Skills: []a2atype.AgentSkill{}, DefaultInputModes: []string{"text"}, DefaultOutputModes: []string{"text"}, } if spec.Type == v1alpha2.AgentType_Declarative && spec.Declarative != nil && spec.Declarative.A2AConfig != nil { - decl := spec.Declarative - card.Skills = slices.Collect(utils.Map(slices.Values(decl.A2AConfig.Skills), func(skill v1alpha2.AgentSkill) server.AgentSkill { - return server.AgentSkill(skill) - })) + card.Skills = make([]a2atype.AgentSkill, 0, len(spec.Declarative.A2AConfig.Skills)) + for _, skill := range spec.Declarative.A2AConfig.Skills { + card.Skills = append(card.Skills, a2atype.AgentSkill{ + ID: skill.ID, + Name: skill.Name, + Description: derefString(skill.Description), + Tags: skill.Tags, + Examples: skill.Examples, + InputModes: skill.InputModes, + OutputModes: skill.OutputModes, + }) + } } return &card } + +func derefString(s *string) string { + if s == nil { + return "" + } + return *s +} diff --git a/go/core/internal/controller/translator/agent/utils_test.go b/go/core/internal/controller/translator/agent/utils_test.go index 5cdc2b3db5..61dd6af647 100644 --- a/go/core/internal/controller/translator/agent/utils_test.go +++ b/go/core/internal/controller/translator/agent/utils_test.go @@ -3,11 +3,10 @@ package agent_test import ( "testing" - a2atype "github.com/a2aproject/a2a-go/a2a" + a2atype "github.com/a2aproject/a2a-go/v2/a2a" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "trpc.group/trpc-go/trpc-a2a-go/server" "github.com/kagent-dev/kagent/go/api/v1alpha2" translator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent" @@ -20,7 +19,7 @@ func TestGetA2AAgentCard(t *testing.T) { wantName string wantDescription string wantURL string - wantSkills []server.AgentSkill + wantSkills []a2atype.AgentSkill }{ { name: "declarative agent with a2a config and skills", @@ -45,7 +44,7 @@ func TestGetA2AAgentCard(t *testing.T) { wantName: "test_agent", wantDescription: "A test agent", wantURL: "http://test-agent.default:8080", - wantSkills: []server.AgentSkill{{Name: "skill-1"}, {Name: "skill-2"}}, + wantSkills: []a2atype.AgentSkill{{Name: "skill-1"}, {Name: "skill-2"}}, }, { name: "declarative agent with nil declarative spec", @@ -62,7 +61,7 @@ func TestGetA2AAgentCard(t *testing.T) { wantName: "nil_declarative", wantDescription: "", wantURL: "http://nil-declarative.default:8080", - wantSkills: []server.AgentSkill{}, + wantSkills: []a2atype.AgentSkill{}, }, { name: "declarative agent with nil a2a config", @@ -81,7 +80,7 @@ func TestGetA2AAgentCard(t *testing.T) { wantName: "no_a2a", wantDescription: "", wantURL: "http://no-a2a.default:8080", - wantSkills: []server.AgentSkill{}, + wantSkills: []a2atype.AgentSkill{}, }, { name: "BYO agent", @@ -97,7 +96,7 @@ func TestGetA2AAgentCard(t *testing.T) { wantName: "byo_agent", wantDescription: "", wantURL: "http://byo-agent.default:8080", - wantSkills: []server.AgentSkill{}, + wantSkills: []a2atype.AgentSkill{}, }, } @@ -108,15 +107,18 @@ func TestGetA2AAgentCard(t *testing.T) { assert.NotNil(t, card) assert.Equal(t, tt.wantName, card.Name) assert.Equal(t, tt.wantDescription, card.Description) - assert.Equal(t, tt.wantURL, card.URL) + require.Len(t, card.SupportedInterfaces, 2) + assert.Equal(t, tt.wantURL, card.SupportedInterfaces[0].URL) + assert.Equal(t, a2atype.TransportProtocolJSONRPC, card.SupportedInterfaces[0].ProtocolBinding) + assert.Equal(t, a2atype.ProtocolVersion("0.3"), card.SupportedInterfaces[0].ProtocolVersion) + assert.Equal(t, tt.wantURL, card.SupportedInterfaces[1].URL) + assert.Equal(t, a2atype.TransportProtocolJSONRPC, card.SupportedInterfaces[1].ProtocolBinding) + assert.Equal(t, a2atype.Version, card.SupportedInterfaces[1].ProtocolVersion) assert.Equal(t, tt.wantSkills, card.Skills) assert.Equal(t, []string{"text"}, card.DefaultInputModes) assert.Equal(t, []string{"text"}, card.DefaultOutputModes) - require.NotNil(t, card.PreferredTransport) - assert.Equal(t, string(a2atype.TransportProtocolJSONRPC), *card.PreferredTransport) - assert.True(t, *card.Capabilities.Streaming) - assert.False(t, *card.Capabilities.PushNotifications) - assert.True(t, *card.Capabilities.StateTransitionHistory) + assert.True(t, card.Capabilities.Streaming) + assert.False(t, card.Capabilities.PushNotifications) }) } } diff --git a/go/core/internal/database/client_postgres.go b/go/core/internal/database/client_postgres.go index 15a2dbacd7..4fa3956c43 100644 --- a/go/core/internal/database/client_postgres.go +++ b/go/core/internal/database/client_postgres.go @@ -8,11 +8,13 @@ import ( "strings" "time" + a2a "github.com/a2aproject/a2a-go/v2/a2a" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" dbpkg "github.com/kagent-dev/kagent/go/api/database" "github.com/kagent-dev/kagent/go/api/v1alpha2" dbgen "github.com/kagent-dev/kagent/go/core/internal/database/gen" + "github.com/kagent-dev/kagent/go/core/pkg/a2acompat/trpcv0" "github.com/pgvector/pgvector-go" "trpc.group/trpc-go/trpc-a2a-go/protocol" ) @@ -197,40 +199,45 @@ func (c *postgresClient) ListEventsForSession(ctx context.Context, sessionID, us // ── Tasks ───────────────────────────────────────────────────────────────────── -func (c *postgresClient) StoreTask(ctx context.Context, task *protocol.Task) error { - data, err := json.Marshal(task) +func (c *postgresClient) StoreTask(ctx context.Context, task *a2a.Task) error { + legacyTask, err := trpcv0.ToLegacyTask(task) + if err != nil { + return fmt.Errorf("failed to convert task to legacy format: %w", err) + } + data, err := json.Marshal(legacyTask) if err != nil { return fmt.Errorf("failed to serialize task: %w", err) } return c.q.UpsertTask(ctx, dbgen.UpsertTaskParams{ - ID: task.ID, - Data: string(data), - SessionID: strPtrIfNotEmpty(task.ContextID), + ID: string(task.ID), + Data: string(data), + SessionID: strPtrIfNotEmpty(task.ContextID), + ProtocolVersion: nil, }) } -func (c *postgresClient) GetTask(ctx context.Context, taskID string) (*protocol.Task, error) { +func (c *postgresClient) GetTask(ctx context.Context, taskID string) (*a2a.Task, error) { row, err := c.q.GetTask(ctx, taskID) if err != nil { return nil, fmt.Errorf("failed to get task %s: %w", taskID, err) } - var task protocol.Task - if err := json.Unmarshal([]byte(row.Data), &task); err != nil { - return nil, fmt.Errorf("failed to deserialize task: %w", err) - } - return &task, nil + return parseVersionedTask(row.Data, row.ProtocolVersion) } -func (c *postgresClient) ListTasksForSession(ctx context.Context, sessionID string) ([]*protocol.Task, error) { +func (c *postgresClient) ListTasksForSession(ctx context.Context, sessionID string) ([]*a2a.Task, error) { rows, err := c.q.ListTasksForSession(ctx, &sessionID) if err != nil { return nil, fmt.Errorf("failed to list tasks for session: %w", err) } - tasks := make([]dbpkg.Task, len(rows)) + tasks := make([]*a2a.Task, 0, len(rows)) for i, r := range rows { - tasks[i] = *toTask(r) + task, err := parseVersionedTask(r.Data, r.ProtocolVersion) + if err != nil { + return nil, fmt.Errorf("failed to parse task row %d: %w", i, err) + } + tasks = append(tasks, task) } - return dbpkg.ParseTasks(tasks) + return tasks, nil } func (c *postgresClient) DeleteTask(ctx context.Context, taskID string) error { @@ -239,42 +246,40 @@ func (c *postgresClient) DeleteTask(ctx context.Context, taskID string) error { // ── Push Notifications ──────────────────────────────────────────────────────── -func (c *postgresClient) StorePushNotification(ctx context.Context, config *protocol.TaskPushNotificationConfig) error { - data, err := json.Marshal(config) +func (c *postgresClient) StorePushNotification(ctx context.Context, config *a2a.PushConfig) error { + legacyConfig := trpcv0.ToLegacyPushConfig(config) + data, err := json.Marshal(legacyConfig) if err != nil { return fmt.Errorf("failed to serialize push notification: %w", err) } return c.q.UpsertPushNotification(ctx, dbgen.UpsertPushNotificationParams{ - ID: config.PushNotificationConfig.ID, - TaskID: config.TaskID, - Data: string(data), + ID: config.ID, + TaskID: string(config.TaskID), + Data: string(data), + ProtocolVersion: nil, }) } -func (c *postgresClient) GetPushNotification(ctx context.Context, taskID, configID string) (*protocol.TaskPushNotificationConfig, error) { +func (c *postgresClient) GetPushNotification(ctx context.Context, taskID, configID string) (*a2a.PushConfig, error) { row, err := c.q.GetPushNotification(ctx, dbgen.GetPushNotificationParams{TaskID: taskID, ID: configID}) if err != nil { return nil, fmt.Errorf("failed to get push notification: %w", err) } - var cfg protocol.TaskPushNotificationConfig - if err := json.Unmarshal([]byte(row.Data), &cfg); err != nil { - return nil, fmt.Errorf("failed to deserialize push notification: %w", err) - } - return &cfg, nil + return parseVersionedPushConfig(row.Data, row.ProtocolVersion) } -func (c *postgresClient) ListPushNotifications(ctx context.Context, taskID string) ([]*protocol.TaskPushNotificationConfig, error) { +func (c *postgresClient) ListPushNotifications(ctx context.Context, taskID string) ([]*a2a.PushConfig, error) { rows, err := c.q.ListPushNotifications(ctx, taskID) if err != nil { return nil, fmt.Errorf("failed to list push notifications: %w", err) } - result := make([]*protocol.TaskPushNotificationConfig, 0, len(rows)) - for _, row := range rows { - var cfg protocol.TaskPushNotificationConfig - if err := json.Unmarshal([]byte(row.Data), &cfg); err != nil { - return nil, fmt.Errorf("failed to deserialize push notification: %w", err) + result := make([]*a2a.PushConfig, 0, len(rows)) + for i, row := range rows { + cfg, err := parseVersionedPushConfig(row.Data, row.ProtocolVersion) + if err != nil { + return nil, fmt.Errorf("failed to deserialize push notification row %d: %w", i, err) } - result = append(result, &cfg) + result = append(result, cfg) } return result, nil } @@ -739,12 +744,13 @@ func toEvent(r dbgen.Event) *dbpkg.Event { func toTask(r dbgen.Task) *dbpkg.Task { return &dbpkg.Task{ - ID: r.ID, - CreatedAt: derefTime(r.CreatedAt), - UpdatedAt: derefTime(r.UpdatedAt), - DeletedAt: r.DeletedAt, - Data: r.Data, - SessionID: derefStr(r.SessionID), + ID: r.ID, + CreatedAt: derefTime(r.CreatedAt), + UpdatedAt: derefTime(r.UpdatedAt), + DeletedAt: r.DeletedAt, + Data: r.Data, + ProtocolVersion: r.ProtocolVersion, + SessionID: derefStr(r.SessionID), } } @@ -866,6 +872,48 @@ func strPtrIfNotEmpty(s string) *string { return &s } +func parseVersionedTask(data string, version *string) (*a2a.Task, error) { + switch { + case version == nil || *version == "": + var legacyTask protocol.Task + if err := json.Unmarshal([]byte(data), &legacyTask); err != nil { + return nil, fmt.Errorf("failed to deserialize legacy task: %w", err) + } + task, err := trpcv0.ToV1Task(&legacyTask) + if err != nil { + return nil, fmt.Errorf("failed to convert legacy task to v1: %w", err) + } + return task, nil + case *version == trpcv0.ProtocolVersionV1: + var task a2a.Task + if err := json.Unmarshal([]byte(data), &task); err != nil { + return nil, fmt.Errorf("failed to deserialize v1 task: %w", err) + } + return &task, nil + default: + return nil, fmt.Errorf("unsupported task protocol_version %q", *version) + } +} + +func parseVersionedPushConfig(data string, version *string) (*a2a.PushConfig, error) { + switch { + case version == nil || *version == "": + var legacyCfg protocol.TaskPushNotificationConfig + if err := json.Unmarshal([]byte(data), &legacyCfg); err != nil { + return nil, fmt.Errorf("failed to deserialize legacy push notification: %w", err) + } + return trpcv0.ToV1PushConfig(&legacyCfg), nil + case *version == trpcv0.ProtocolVersionV1: + var cfg a2a.PushConfig + if err := json.Unmarshal([]byte(data), &cfg); err != nil { + return nil, fmt.Errorf("failed to deserialize v1 push notification: %w", err) + } + return &cfg, nil + default: + return nil, fmt.Errorf("unsupported push_notification protocol_version %q", *version) + } +} + func derefStr(s *string) string { if s != nil { return *s diff --git a/go/core/internal/database/client_test.go b/go/core/internal/database/client_test.go index b8493396ec..0768cddb97 100644 --- a/go/core/internal/database/client_test.go +++ b/go/core/internal/database/client_test.go @@ -7,13 +7,13 @@ import ( "testing" "time" + a2a "github.com/a2aproject/a2a-go/v2/a2a" "github.com/jackc/pgx/v5/pgxpool" dbpkg "github.com/kagent-dev/kagent/go/api/database" "github.com/kagent-dev/kagent/go/api/v1alpha2" "github.com/pgvector/pgvector-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "trpc.group/trpc-go/trpc-a2a-go/protocol" ) // TestConcurrentAgentUpserts verifies that concurrent StoreAgent calls @@ -325,7 +325,7 @@ func TestStoreTaskTouchesSessionActivity(t *testing.T) { require.NoError(t, err) time.Sleep(10 * time.Millisecond) - err = client.StoreTask(ctx, &protocol.Task{ + err = client.StoreTask(ctx, &a2a.Task{ ID: "task-1", ContextID: sessionID, }) diff --git a/go/core/internal/database/gen/models.go b/go/core/internal/database/gen/models.go index e05efa2ae8..20b8fb7a39 100644 --- a/go/core/internal/database/gen/models.go +++ b/go/core/internal/database/gen/models.go @@ -106,12 +106,13 @@ type Memory struct { } type PushNotification struct { - ID string - TaskID string - CreatedAt *time.Time - UpdatedAt *time.Time - DeletedAt *time.Time - Data string + ID string + TaskID string + CreatedAt *time.Time + UpdatedAt *time.Time + DeletedAt *time.Time + Data string + ProtocolVersion *string } type Session struct { @@ -126,12 +127,13 @@ type Session struct { } type Task struct { - ID string - CreatedAt *time.Time - UpdatedAt *time.Time - DeletedAt *time.Time - Data string - SessionID *string + ID string + CreatedAt *time.Time + UpdatedAt *time.Time + DeletedAt *time.Time + Data string + SessionID *string + ProtocolVersion *string } type Tool struct { diff --git a/go/core/internal/database/gen/push_notifications.sql.go b/go/core/internal/database/gen/push_notifications.sql.go index 73a7a00691..c502c58c13 100644 --- a/go/core/internal/database/gen/push_notifications.sql.go +++ b/go/core/internal/database/gen/push_notifications.sql.go @@ -10,7 +10,7 @@ import ( ) const getPushNotification = `-- name: GetPushNotification :one -SELECT id, task_id, created_at, updated_at, deleted_at, data FROM push_notification +SELECT id, task_id, created_at, updated_at, deleted_at, data, protocol_version FROM push_notification WHERE task_id = $1 AND id = $2 AND deleted_at IS NULL LIMIT 1 ` @@ -30,12 +30,13 @@ func (q *Queries) GetPushNotification(ctx context.Context, arg GetPushNotificati &i.UpdatedAt, &i.DeletedAt, &i.Data, + &i.ProtocolVersion, ) return i, err } const listPushNotifications = `-- name: ListPushNotifications :many -SELECT id, task_id, created_at, updated_at, deleted_at, data FROM push_notification +SELECT id, task_id, created_at, updated_at, deleted_at, data, protocol_version FROM push_notification WHERE task_id = $1 AND deleted_at IS NULL ORDER BY created_at ASC ` @@ -56,6 +57,7 @@ func (q *Queries) ListPushNotifications(ctx context.Context, taskID string) ([]P &i.UpdatedAt, &i.DeletedAt, &i.Data, + &i.ProtocolVersion, ); err != nil { return nil, err } @@ -78,20 +80,27 @@ func (q *Queries) SoftDeletePushNotification(ctx context.Context, taskID string) } const upsertPushNotification = `-- name: UpsertPushNotification :exec -INSERT INTO push_notification (id, task_id, data, created_at, updated_at) -VALUES ($1, $2, $3, NOW(), NOW()) +INSERT INTO push_notification (id, task_id, data, protocol_version, created_at, updated_at) +VALUES ($1, $2, $3, $4, NOW(), NOW()) ON CONFLICT (id) DO UPDATE SET - data = EXCLUDED.data, - updated_at = NOW() + data = EXCLUDED.data, + protocol_version = EXCLUDED.protocol_version, + updated_at = NOW() ` type UpsertPushNotificationParams struct { - ID string - TaskID string - Data string + ID string + TaskID string + Data string + ProtocolVersion *string } func (q *Queries) UpsertPushNotification(ctx context.Context, arg UpsertPushNotificationParams) error { - _, err := q.db.Exec(ctx, upsertPushNotification, arg.ID, arg.TaskID, arg.Data) + _, err := q.db.Exec(ctx, upsertPushNotification, + arg.ID, + arg.TaskID, + arg.Data, + arg.ProtocolVersion, + ) return err } diff --git a/go/core/internal/database/gen/tasks.sql.go b/go/core/internal/database/gen/tasks.sql.go index e91be6daaf..be4e93d752 100644 --- a/go/core/internal/database/gen/tasks.sql.go +++ b/go/core/internal/database/gen/tasks.sql.go @@ -10,7 +10,7 @@ import ( ) const getTask = `-- name: GetTask :one -SELECT id, created_at, updated_at, deleted_at, data, session_id FROM task +SELECT id, created_at, updated_at, deleted_at, data, session_id, protocol_version FROM task WHERE id = $1 AND deleted_at IS NULL LIMIT 1 ` @@ -25,12 +25,13 @@ func (q *Queries) GetTask(ctx context.Context, id string) (Task, error) { &i.DeletedAt, &i.Data, &i.SessionID, + &i.ProtocolVersion, ) return i, err } const listTasksForSession = `-- name: ListTasksForSession :many -SELECT id, created_at, updated_at, deleted_at, data, session_id FROM task +SELECT id, created_at, updated_at, deleted_at, data, session_id, protocol_version FROM task WHERE session_id = $1 AND deleted_at IS NULL ORDER BY created_at ASC ` @@ -51,6 +52,7 @@ func (q *Queries) ListTasksForSession(ctx context.Context, sessionID *string) ([ &i.DeletedAt, &i.Data, &i.SessionID, + &i.ProtocolVersion, ); err != nil { return nil, err } @@ -86,12 +88,13 @@ func (q *Queries) TaskExists(ctx context.Context, id string) (bool, error) { const upsertTask = `-- name: UpsertTask :exec WITH upserted_task AS ( -INSERT INTO task (id, data, session_id, created_at, updated_at) -VALUES ($1, $2, $3, NOW(), NOW()) +INSERT INTO task (id, data, session_id, protocol_version, created_at, updated_at) +VALUES ($1, $2, $3, $4, NOW(), NOW()) ON CONFLICT (id) DO UPDATE SET - data = EXCLUDED.data, - session_id = EXCLUDED.session_id, - updated_at = NOW() + data = EXCLUDED.data, + session_id = EXCLUDED.session_id, + protocol_version = EXCLUDED.protocol_version, + updated_at = NOW() RETURNING session_id ) UPDATE session @@ -103,12 +106,18 @@ WHERE upserted_task.session_id IS NOT NULL ` type UpsertTaskParams struct { - ID string - Data string - SessionID *string + ID string + Data string + SessionID *string + ProtocolVersion *string } func (q *Queries) UpsertTask(ctx context.Context, arg UpsertTaskParams) error { - _, err := q.db.Exec(ctx, upsertTask, arg.ID, arg.Data, arg.SessionID) + _, err := q.db.Exec(ctx, upsertTask, + arg.ID, + arg.Data, + arg.SessionID, + arg.ProtocolVersion, + ) return err } diff --git a/go/core/internal/database/queries/push_notifications.sql b/go/core/internal/database/queries/push_notifications.sql index ccc7553f69..d830497f97 100644 --- a/go/core/internal/database/queries/push_notifications.sql +++ b/go/core/internal/database/queries/push_notifications.sql @@ -9,11 +9,12 @@ WHERE task_id = $1 AND deleted_at IS NULL ORDER BY created_at ASC; -- name: UpsertPushNotification :exec -INSERT INTO push_notification (id, task_id, data, created_at, updated_at) -VALUES ($1, $2, $3, NOW(), NOW()) +INSERT INTO push_notification (id, task_id, data, protocol_version, created_at, updated_at) +VALUES ($1, $2, $3, $4, NOW(), NOW()) ON CONFLICT (id) DO UPDATE SET - data = EXCLUDED.data, - updated_at = NOW(); + data = EXCLUDED.data, + protocol_version = EXCLUDED.protocol_version, + updated_at = NOW(); -- name: SoftDeletePushNotification :exec UPDATE push_notification SET deleted_at = NOW() diff --git a/go/core/internal/database/queries/tasks.sql b/go/core/internal/database/queries/tasks.sql index 66105c6f3a..d41fcdf928 100644 --- a/go/core/internal/database/queries/tasks.sql +++ b/go/core/internal/database/queries/tasks.sql @@ -15,12 +15,13 @@ ORDER BY created_at ASC; -- name: UpsertTask :exec WITH upserted_task AS ( -INSERT INTO task (id, data, session_id, created_at, updated_at) -VALUES ($1, $2, $3, NOW(), NOW()) +INSERT INTO task (id, data, session_id, protocol_version, created_at, updated_at) +VALUES ($1, $2, $3, $4, NOW(), NOW()) ON CONFLICT (id) DO UPDATE SET - data = EXCLUDED.data, - session_id = EXCLUDED.session_id, - updated_at = NOW() + data = EXCLUDED.data, + session_id = EXCLUDED.session_id, + protocol_version = EXCLUDED.protocol_version, + updated_at = NOW() RETURNING session_id ) UPDATE session diff --git a/go/core/internal/httpserver/handlers/a2a_version.go b/go/core/internal/httpserver/handlers/a2a_version.go new file mode 100644 index 0000000000..fcb715d52f --- /dev/null +++ b/go/core/internal/httpserver/handlers/a2a_version.go @@ -0,0 +1,29 @@ +package handlers + +import ( + "fmt" + "net/http" + + a2a "github.com/a2aproject/a2a-go/v2/a2a" +) + +type a2aWireVersion string + +const ( + a2aWireV0 a2aWireVersion = "v0" + a2aWireV1 a2aWireVersion = "v1" +) + +// negotiatedA2AWireVersion returns the A2A wire version negotiated by the client. +// Uses the constants exposed by a2a-go so this function is reusable for all versions in the future +func negotiatedA2AWireVersion(r *http.Request) (a2aWireVersion, error) { + version := r.Header.Get(a2a.SvcParamVersion) + switch version { + case "": + return a2aWireV0, nil + case string(a2a.Version): + return a2aWireV1, nil + default: + return "", fmt.Errorf("unsupported A2A version %q", version) + } +} diff --git a/go/core/internal/httpserver/handlers/agents.go b/go/core/internal/httpserver/handlers/agents.go index 59c68ce27f..25806f3b60 100644 --- a/go/core/internal/httpserver/handlers/agents.go +++ b/go/core/internal/httpserver/handlers/agents.go @@ -18,7 +18,6 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - utilvalidation "k8s.io/apimachinery/pkg/util/validation" "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -33,46 +32,35 @@ func NewAgentsHandler(base *Base) *AgentsHandler { return &AgentsHandler{Base: base} } -// HandleListAgents handles GET /api/agents requests using database. -// Optional query param: namespace=. +// HandleListAgents handles GET /api/agents requests using database func (h *AgentsHandler) HandleListAgents(w ErrorResponseWriter, r *http.Request) { log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "list-db") - namespace := r.URL.Query().Get("namespace") - if namespace == "" { - h.handleListAgents(w, r, log) - return - } - - if strings.TrimSpace(namespace) != namespace { - w.RespondWithError(errors.NewBadRequestError( - fmt.Sprintf("invalid namespace %q: must not contain leading or trailing whitespace", namespace), - nil, - )) + if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent"}); err != nil { + w.RespondWithError(err) return } - if errs := utilvalidation.IsDNS1123Label(namespace); len(errs) > 0 { - w.RespondWithError(errors.NewBadRequestError( - fmt.Sprintf("invalid namespace %q: %s", namespace, strings.Join(errs, "; ")), - nil, - )) + agentList := &v1alpha2.AgentList{} + if err := h.KubeClient.List(r.Context(), agentList); err != nil { + w.RespondWithError(errors.NewInternalServerError("Failed to list Agents from Kubernetes", err)) return } - h.handleListAgents(w, r, log.WithValues("namespace", namespace), client.InNamespace(namespace)) -} + agentsWithID := make([]api.AgentResponse, 0) + h.appendAgentResponses(r.Context(), log, agentObjects(agentList.Items), &agentsWithID) -func (h *AgentsHandler) handleListAgents(w ErrorResponseWriter, r *http.Request, log logr.Logger, opts ...client.ListOption) { - if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent"}); err != nil { - w.RespondWithError(err) + harnessList := &v1alpha2.AgentHarnessList{} + if err := h.KubeClient.List(r.Context(), harnessList); err != nil { + w.RespondWithError(errors.NewInternalServerError("Failed to list AgentHarness resources from Kubernetes", err)) return } - - agentsWithID, err := h.listAgentResponses(r.Context(), log, opts...) - if err != nil { - w.RespondWithError(err) - return + for i := range harnessList.Items { + sb := &harnessList.Items[i] + if sb.Spec.Backend != v1alpha2.AgentHarnessBackendOpenClaw && sb.Spec.Backend != v1alpha2.AgentHarnessBackendNemoClaw { + continue + } + agentsWithID = append(agentsWithID, h.openshellAgentHarnessAgentResponse(r.Context(), log, sb)) } log.Info("Successfully listed agents", "count", len(agentsWithID)) diff --git a/go/core/internal/httpserver/handlers/agents_test.go b/go/core/internal/httpserver/handlers/agents_test.go index efe2bf352c..e16da1bd7e 100644 --- a/go/core/internal/httpserver/handlers/agents_test.go +++ b/go/core/internal/httpserver/handlers/agents_test.go @@ -500,92 +500,6 @@ func TestHandleListAgents(t *testing.T) { } require.True(t, found) }) - - t.Run("filters Agent and AgentHarness rows by namespace query parameter", func(t *testing.T) { - modelConfig := createTestModelConfig() - agentDefault := createTestAgent("agent-in-default", modelConfig) - agentOther := &v1alpha2.Agent{ - ObjectMeta: metav1.ObjectMeta{Name: "agent-in-other", Namespace: "other"}, - Spec: v1alpha2.AgentSpec{ - Type: v1alpha2.AgentType_Declarative, - Declarative: &v1alpha2.DeclarativeAgentSpec{ - ModelConfig: modelConfig.Name, - }, - }, - } - harnessDefault := &v1alpha2.AgentHarness{ - ObjectMeta: metav1.ObjectMeta{Name: "harness-default", Namespace: "default"}, - Spec: v1alpha2.AgentHarnessSpec{ - Backend: v1alpha2.AgentHarnessBackendOpenClaw, - ModelConfigRef: "test-model-config", - }, - } - harnessOther := &v1alpha2.AgentHarness{ - ObjectMeta: metav1.ObjectMeta{Name: "harness-other", Namespace: "other"}, - Spec: v1alpha2.AgentHarnessSpec{ - Backend: v1alpha2.AgentHarnessBackendOpenClaw, - ModelConfigRef: "test-model-config", - }, - } - unsupportedHarnessDefault := &v1alpha2.AgentHarness{ - ObjectMeta: metav1.ObjectMeta{Name: "unsupported-harness", Namespace: "default"}, - Spec: v1alpha2.AgentHarnessSpec{ - Backend: v1alpha2.AgentHarnessBackendType("unsupported"), - ModelConfigRef: "test-model-config", - }, - } - handler, _ := setupTestHandler(t, agentDefault, agentOther, harnessDefault, harnessOther, unsupportedHarnessDefault, modelConfig) - - req := httptest.NewRequest("GET", "/api/agents?namespace=default", nil) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleListAgents(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - var response api.StandardResponse[[]api.AgentResponse] - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &response)) - require.Len(t, response.Data, 2) - - byName := make(map[string]api.AgentResponse, len(response.Data)) - for _, row := range response.Data { - byName[row.Agent.Metadata.Name] = row - require.Equal(t, "default", row.Agent.Metadata.Namespace) - } - require.Contains(t, byName, "agent-in-default") - require.Contains(t, byName, "harness-default") - require.NotContains(t, byName, "agent-in-other") - require.NotContains(t, byName, "harness-other") - require.NotContains(t, byName, "unsupported-harness") - }) - - // Kubernetes namespace names must be DNS-1123 labels. Rejecting invalid input - // before calling the Kubernetes client keeps the list path consistent with - // other resource handlers and avoids surprising cross-namespace behavior. - t.Run("returns 400 for invalid namespace query value", func(t *testing.T) { - handler, _ := setupTestHandler(t) - - req := httptest.NewRequest("GET", "/api/agents?namespace=INVALID_NS!", nil) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleListAgents(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusBadRequest, w.Code) - }) - - t.Run("returns 400 for namespace query value with leading or trailing whitespace", func(t *testing.T) { - handler, _ := setupTestHandler(t) - - req := httptest.NewRequest("GET", "/api/agents?namespace=%20default", nil) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleListAgents(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusBadRequest, w.Code) - require.Contains(t, w.Body.String(), "must not contain leading or trailing whitespace") - }) } func TestHandleListSandboxAgents(t *testing.T) { diff --git a/go/core/internal/httpserver/handlers/sessions.go b/go/core/internal/httpserver/handlers/sessions.go index 1ff768077d..05cd761ddc 100644 --- a/go/core/internal/httpserver/handlers/sessions.go +++ b/go/core/internal/httpserver/handlers/sessions.go @@ -6,13 +6,14 @@ import ( "strconv" "time" + a2a "github.com/a2aproject/a2a-go/v2/a2a" "github.com/kagent-dev/kagent/go/api/database" api "github.com/kagent-dev/kagent/go/api/httpapi" "github.com/kagent-dev/kagent/go/api/v1alpha2" "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" "github.com/kagent-dev/kagent/go/core/internal/utils" + "github.com/kagent-dev/kagent/go/core/pkg/a2acompat/trpcv0" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - "trpc.group/trpc-go/trpc-a2a-go/protocol" ) // SessionsHandler handles session-related requests @@ -119,7 +120,7 @@ func (h *SessionsHandler) HandleCreateSession(w ErrorResponseWriter, r *http.Req } log = log.WithValues("agentRef", *sessionRequest.AgentRef) - id := protocol.GenerateContextID() + id := a2a.NewContextID() if sessionRequest.ID != nil && *sessionRequest.ID != "" { id = *sessionRequest.ID } @@ -346,10 +347,32 @@ func (h *SessionsHandler) HandleListTasksForSession(w ErrorResponseWriter, r *ht w.RespondWithError(errors.NewInternalServerError("Failed to get session runs", err)) return } + wireVersion, err := negotiatedA2AWireVersion(r) + if err != nil { + w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", err)) + return + } log.Info("Successfully retrieved session tasks", "count", len(tasks)) - data := api.NewResponse(tasks, "Successfully retrieved session tasks", false) - RespondWithJSON(w, http.StatusOK, data) + switch wireVersion { + case a2aWireV0: + legacyTasks := make([]any, 0, len(tasks)) + for i := range tasks { + legacyTask, convErr := trpcv0.ToLegacyTask(tasks[i]) + if convErr != nil { + w.RespondWithError(errors.NewInternalServerError("Failed to convert task", convErr)) + return + } + legacyTasks = append(legacyTasks, legacyTask) + } + data := api.NewResponse(legacyTasks, "Successfully retrieved session tasks", false) + RespondWithJSON(w, http.StatusOK, data) + case a2aWireV1: + data := api.NewResponse(tasks, "Successfully retrieved session tasks", false) + RespondWithJSON(w, http.StatusOK, data) + default: + w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", fmt.Errorf("unknown negotiated wire version %q", wireVersion))) + } } func (h *SessionsHandler) HandleAddEventToSession(w ErrorResponseWriter, r *http.Request) { diff --git a/go/core/internal/httpserver/handlers/sessions_test.go b/go/core/internal/httpserver/handlers/sessions_test.go index 02ee3243d2..3e5627564d 100644 --- a/go/core/internal/httpserver/handlers/sessions_test.go +++ b/go/core/internal/httpserver/handlers/sessions_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + a2a "github.com/a2aproject/a2a-go/v2/a2a" "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -24,7 +25,6 @@ import ( "github.com/kagent-dev/kagent/go/core/internal/utils" "github.com/kagent-dev/kagent/go/core/pkg/auth" "github.com/kagent-dev/kmcp/api/v1alpha1" - "trpc.group/trpc-go/trpc-a2a-go/protocol" ) func setUser(req *http.Request, userID string) *http.Request { @@ -532,24 +532,25 @@ func TestSessionsHandler(t *testing.T) { agentID := "1" createTestSession(t, dbClient, sessionID, userID, agentID) - require.NoError(t, dbClient.StoreTask(context.Background(), &protocol.Task{ + require.NoError(t, dbClient.StoreTask(context.Background(), &a2a.Task{ ID: "task-1", ContextID: sessionID, })) - require.NoError(t, dbClient.StoreTask(context.Background(), &protocol.Task{ + require.NoError(t, dbClient.StoreTask(context.Background(), &a2a.Task{ ID: "task-2", ContextID: sessionID, })) req := httptest.NewRequest("GET", "/api/sessions/"+sessionID+"/tasks", nil) req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) + req.Header.Set("A2A-Version", "1.0") req = setUser(req, userID) handler.HandleListTasksForSession(responseRecorder, req) assert.Equal(t, http.StatusOK, responseRecorder.Code) - var response api.StandardResponse[[]*protocol.Task] + var response api.StandardResponse[[]*a2a.Task] err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) require.NoError(t, err) assert.Len(t, response.Data, 2) diff --git a/go/core/internal/httpserver/handlers/tasks.go b/go/core/internal/httpserver/handlers/tasks.go index ccf0ee6e21..46a08f7a73 100644 --- a/go/core/internal/httpserver/handlers/tasks.go +++ b/go/core/internal/httpserver/handlers/tasks.go @@ -1,10 +1,13 @@ package handlers import ( + "fmt" "net/http" + a2a "github.com/a2aproject/a2a-go/v2/a2a" api "github.com/kagent-dev/kagent/go/api/httpapi" "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" + "github.com/kagent-dev/kagent/go/core/pkg/a2acompat/trpcv0" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" "trpc.group/trpc-go/trpc-a2a-go/protocol" ) @@ -34,22 +37,68 @@ func (h *TasksHandler) HandleGetTask(w ErrorResponseWriter, r *http.Request) { w.RespondWithError(errors.NewNotFoundError("Task not found", err)) return } + wireVersion, err := negotiatedA2AWireVersion(r) + if err != nil { + w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", err)) + return + } log.Info("Successfully retrieved task") - data := api.NewResponse(task, "Successfully retrieved task", false) - RespondWithJSON(w, http.StatusOK, data) + var data any + switch wireVersion { + case a2aWireV0: + legacyTask, convErr := trpcv0.ToLegacyTask(task) + if convErr != nil { + w.RespondWithError(errors.NewInternalServerError("Failed to convert task", convErr)) + return + } + data = legacyTask + case a2aWireV1: + data = task + default: + w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", fmt.Errorf("unknown negotiated wire version %q", wireVersion))) + return + } + response := api.NewResponse(data, "Successfully retrieved task", false) + RespondWithJSON(w, http.StatusOK, response) } func (h *TasksHandler) HandleCreateTask(w ErrorResponseWriter, r *http.Request) { log := ctrllog.FromContext(r.Context()).WithName("tasks-handler").WithValues("operation", "create-task") - task := protocol.Task{} - if err := DecodeJSONBody(r, &task); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) + wireVersion, err := negotiatedA2AWireVersion(r) + if err != nil { + w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", err)) + return + } + + task := a2a.Task{} + switch wireVersion { + case a2aWireV0: + legacyTask := protocol.Task{} + if err := DecodeJSONBody(r, &legacyTask); err != nil { + w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) + return + } + converted, convErr := trpcv0.ToV1Task(&legacyTask) + if convErr != nil { + w.RespondWithError(errors.NewBadRequestError("Invalid legacy task payload", convErr)) + return + } + if converted != nil { + task = *converted + } + case a2aWireV1: + if err := DecodeJSONBody(r, &task); err != nil { + w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) + return + } + default: + w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", fmt.Errorf("unknown negotiated wire version %q", wireVersion))) return } if task.ID == "" { - task.ID = protocol.GenerateTaskID() + task.ID = a2a.NewTaskID() } log = log.WithValues("task_id", task.ID) @@ -59,8 +108,23 @@ func (h *TasksHandler) HandleCreateTask(w ErrorResponseWriter, r *http.Request) } log.Info("Successfully created task") - data := api.NewResponse(task, "Successfully created task", false) - RespondWithJSON(w, http.StatusCreated, data) + var data any + switch wireVersion { + case a2aWireV0: + legacyTask, convErr := trpcv0.ToLegacyTask(&task) + if convErr != nil { + w.RespondWithError(errors.NewInternalServerError("Failed to convert task", convErr)) + return + } + data = legacyTask + case a2aWireV1: + data = task + default: + w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", fmt.Errorf("unknown negotiated wire version %q", wireVersion))) + return + } + response := api.NewResponse(data, "Successfully created task", false) + RespondWithJSON(w, http.StatusCreated, response) } func (h *TasksHandler) HandleDeleteTask(w ErrorResponseWriter, r *http.Request) { diff --git a/go/core/internal/mcp/mcp_handler.go b/go/core/internal/mcp/mcp_handler.go index 804228cf0a..ff3d03b14f 100644 --- a/go/core/internal/mcp/mcp_handler.go +++ b/go/core/internal/mcp/mcp_handler.go @@ -2,25 +2,24 @@ package mcp import ( "context" + "encoding/json" "fmt" "net/http" "strings" "sync" "time" - "github.com/google/jsonschema-go/jsonschema" + a2atype "github.com/a2aproject/a2a-go/v2/a2a" + a2aclientv2 "github.com/a2aproject/a2a-go/v2/a2aclient" + "github.com/a2aproject/a2a-go/v2/a2aclient/agentcard" "github.com/kagent-dev/kagent/go/api/v1alpha2" "github.com/kagent-dev/kagent/go/core/internal/a2a" - authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" "github.com/kagent-dev/kagent/go/core/internal/version" "github.com/kagent-dev/kagent/go/core/pkg/auth" - "github.com/kagent-dev/kagent/go/core/pkg/env" mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - a2aclient "trpc.group/trpc-go/trpc-a2a-go/client" - "trpc.group/trpc-go/trpc-a2a-go/protocol" ) // MCPHandler handles MCP requests and bridges them to A2A endpoints @@ -83,21 +82,12 @@ func NewMCPHandler(kubeClient client.Client, a2aBaseURL string, authenticator au server := mcpsdk.NewServer(impl, nil) handler.server = server - // Add list_agents tool. - // InputSchema is set explicitly (rather than reflected from the empty - // ListAgentsInput struct) so the serialized schema includes "properties": {}. - // OpenAI strict mode rejects object schemas without a properties key. - // See https://github.com/kagent-dev/kagent/issues/1889. + // Add list_agents tool mcpsdk.AddTool[ListAgentsInput, ListAgentsOutput]( server, &mcpsdk.Tool{ Name: "list_agents", Description: "List invokable kagent agents (accepted + deploymentReady)", - InputSchema: &jsonschema.Schema{ - Type: "object", - Properties: map[string]*jsonschema.Schema{}, - AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}}, - }, }, handler.handleListAgents, ) @@ -113,15 +103,11 @@ func NewMCPHandler(kubeClient client.Client, a2aBaseURL string, authenticator au ) // Create HTTP handler - var httpOpts *mcpsdk.StreamableHTTPOptions - if env.KagentMCPStateless.Get() { - httpOpts = &mcpsdk.StreamableHTTPOptions{Stateless: true} - } handler.httpHandler = mcpsdk.NewStreamableHTTPHandler( func(*http.Request) *mcpsdk.Server { return server }, - httpOpts, + nil, ) return handler, nil @@ -211,38 +197,39 @@ func (h *MCPHandler) handleInvokeAgent(ctx context.Context, req *mcpsdk.CallTool agentRef := agentNS + "/" + agentName agentNns := types.NamespacedName{Namespace: agentNS, Name: agentName} - // Get context ID from client request (stateless mode) - // If not provided, contextIDPtr will be nil and a new conversation will start - var contextIDPtr *string - if input.ContextID != "" { - contextIDPtr = &input.ContextID - log.V(1).Info("Using context_id from client request", "context_id", input.ContextID) - } - // Get or create cached A2A client for this agent a2aURL := fmt.Sprintf("%s/%s/", h.a2aBaseURL, agentRef) - var a2aClient *a2aclient.A2AClient + var a2aClient *a2aclientv2.Client if cached, ok := h.a2aClients.Load(agentRef); ok { - if client, ok := cached.(*a2aclient.A2AClient); ok { + if client, ok := cached.(*a2aclientv2.Client); ok { a2aClient = client } } // Create new client if not cached if a2aClient == nil { - // Build A2A client options with authentication propagation - a2aOpts := []a2aclient.Option{ - a2aclient.WithTimeout(h.a2aTimeout), - a2aclient.WithHTTPReqHandler( - authimpl.A2ARequestHandler( - h.authenticator, - agentNns, - ), - ), + httpClient := &http.Client{Timeout: h.a2aTimeout} + resolver := agentcard.NewResolver(httpClient) + card, err := resolver.Resolve(ctx, a2aURL) + if err != nil { + log.Error(err, "Failed to resolve A2A card", "agent", agentRef) + return &mcpsdk.CallToolResult{ + Content: []mcpsdk.Content{ + &mcpsdk.TextContent{Text: fmt.Sprintf("Failed to resolve A2A card: %v", err)}, + }, + IsError: true, + }, InvokeAgentOutput{}, nil } - - newClient, err := a2aclient.NewA2AClient(a2aURL, a2aOpts...) + newClient, err := a2aclientv2.NewFromCard( + ctx, + card, + a2aclientv2.WithJSONRPCTransport(httpClient), + a2aclientv2.WithCallInterceptors( + a2a.NewUpstreamAuthInterceptor(h.authenticator, agentNns), + a2a.NewStaticHeadersInterceptor(map[string]string{"A2A-Version": string(a2atype.Version)}), + ), + ) if err != nil { log.Error(err, "Failed to create A2A client", "agent", agentRef) return &mcpsdk.CallToolResult{ @@ -258,15 +245,9 @@ func (h *MCPHandler) handleInvokeAgent(ctx context.Context, req *mcpsdk.CallTool a2aClient = newClient } - // Send message via A2A - result, err := a2aClient.SendMessage(ctx, protocol.SendMessageParams{ - Message: protocol.Message{ - Kind: protocol.KindMessage, - Role: protocol.MessageRoleUser, - ContextID: contextIDPtr, - Parts: []protocol.Part{protocol.NewTextPart(input.Task)}, - }, - }) + message := a2atype.NewMessage(a2atype.MessageRoleUser, a2atype.NewTextPart(input.Task)) + message.ContextID = input.ContextID + result, err := a2aClient.SendMessage(ctx, &a2atype.SendMessageRequest{Message: message}) if err != nil { log.Error(err, "Failed to send A2A message", "agent", agentRef) return &mcpsdk.CallToolResult{ @@ -279,25 +260,22 @@ func (h *MCPHandler) handleInvokeAgent(ctx context.Context, req *mcpsdk.CallTool // Extract response text and context ID var responseText, newContextID string - switch a2aResult := result.Result.(type) { - case *protocol.Message: - responseText = a2a.ExtractText(*a2aResult) - if a2aResult.ContextID != nil { - newContextID = *a2aResult.ContextID - } - // Kagent A2A only returns Task type for now - case *protocol.Task: + switch a2aResult := result.(type) { + case *a2atype.Message: + responseText = a2a.ExtractText(a2aResult) + newContextID = a2aResult.ContextID + case *a2atype.Task: newContextID = a2aResult.ContextID if a2aResult.Status.Message != nil { - responseText = a2a.ExtractText(*a2aResult.Status.Message) + responseText = a2a.ExtractText(a2aResult.Status.Message) } for _, artifact := range a2aResult.Artifacts { - responseText += a2a.ExtractText(protocol.Message{Parts: artifact.Parts}) + responseText += a2a.ExtractText(&a2atype.Message{Parts: artifact.Parts}) } } if responseText == "" { - raw, err := result.MarshalJSON() + raw, err := json.Marshal(result) if err != nil { return &mcpsdk.CallToolResult{ Content: []mcpsdk.Content{ diff --git a/go/core/pkg/a2acompat/trpcv0/convert.go b/go/core/pkg/a2acompat/trpcv0/convert.go new file mode 100644 index 0000000000..15f3629b26 --- /dev/null +++ b/go/core/pkg/a2acompat/trpcv0/convert.go @@ -0,0 +1,565 @@ +package trpcv0 + +import ( + "encoding/json" + "fmt" + "maps" + "time" + + legacya2a "github.com/a2aproject/a2a-go/a2a" + a2av1 "github.com/a2aproject/a2a-go/v2/a2a" + a2av0 "github.com/a2aproject/a2a-go/v2/a2acompat/a2av0" + trpc "trpc.group/trpc-go/trpc-a2a-go/protocol" +) + +const ProtocolVersionV1 = string(a2av1.Version) + +// TaskJSONToV1JSON converts a persisted trpc-a2a-go task blob to official A2A v1 JSON. +func TaskJSONToV1JSON(data []byte) ([]byte, error) { + var task trpc.Task + if err := json.Unmarshal(data, &task); err != nil { + return nil, fmt.Errorf("unmarshal trpc task: %w", err) + } + v1, err := ToV1Task(&task) + if err != nil { + return nil, err + } + return json.Marshal(v1) +} + +// PushNotificationJSONToV1JSON converts a persisted trpc-a2a-go push config blob to official A2A v1 JSON. +func PushNotificationJSONToV1JSON(data []byte) ([]byte, error) { + var cfg trpc.TaskPushNotificationConfig + if err := json.Unmarshal(data, &cfg); err != nil { + return nil, fmt.Errorf("unmarshal trpc push notification config: %w", err) + } + v1 := ToV1PushConfig(&cfg) + return json.Marshal(v1) +} + +func ToV1Task(task *trpc.Task) (*a2av1.Task, error) { + v0, err := ToOfficialV0Task(task) + if err != nil { + return nil, err + } + return a2av0.ToV1Task(v0) +} + +func ToV1PushConfig(cfg *trpc.TaskPushNotificationConfig) *a2av1.PushConfig { + return a2av0.ToV1PushConfig(ToOfficialV0PushConfig(cfg)) +} + +func ToLegacyTask(task *a2av1.Task) (*trpc.Task, error) { + if task == nil { + return nil, nil + } + message, err := toLegacyMessage(task.Status.Message) + if err != nil { + return nil, fmt.Errorf("convert status message: %w", err) + } + + result := &trpc.Task{ + ID: string(task.ID), + ContextID: task.ContextID, + Metadata: task.Metadata, + Status: trpc.TaskStatus{ + State: toLegacyTaskState(task.Status.State), + Message: message, + Timestamp: formatTimestamp(task.Status.Timestamp), + }, + } + + if len(task.History) > 0 { + result.History = make([]trpc.Message, 0, len(task.History)) + for i := range task.History { + msg, convErr := toLegacyMessage(task.History[i]) + if convErr != nil { + return nil, fmt.Errorf("convert history message %d: %w", i, convErr) + } + if msg != nil { + result.History = append(result.History, *msg) + } + } + } + if len(task.Artifacts) > 0 { + result.Artifacts = make([]trpc.Artifact, 0, len(task.Artifacts)) + for i := range task.Artifacts { + artifact, convErr := toLegacyArtifact(task.Artifacts[i]) + if convErr != nil { + return nil, fmt.Errorf("convert artifact %d: %w", i, convErr) + } + if artifact != nil { + result.Artifacts = append(result.Artifacts, *artifact) + } + } + } + return result, nil +} + +func ToLegacyPushConfig(cfg *a2av1.PushConfig) *trpc.TaskPushNotificationConfig { + if cfg == nil { + return nil + } + result := &trpc.TaskPushNotificationConfig{ + TaskID: string(cfg.TaskID), + PushNotificationConfig: trpc.PushNotificationConfig{ + ID: cfg.ID, + URL: cfg.URL, + Token: cfg.Token, + }, + } + if cfg.Auth != nil { + credentials := cfg.Auth.Credentials + schemes := []string{} + if cfg.Auth.Scheme != "" { + schemes = append(schemes, cfg.Auth.Scheme) + } + result.PushNotificationConfig.Authentication = &trpc.AuthenticationInfo{ + Credentials: &credentials, + Schemes: schemes, + } + } + return result +} + +func ToOfficialV0Task(task *trpc.Task) (*legacya2a.Task, error) { + if task == nil { + return nil, nil + } + status, err := ToOfficialV0TaskStatus(task.Status) + if err != nil { + return nil, err + } + result := &legacya2a.Task{ + ID: legacya2a.TaskID(task.ID), + ContextID: task.ContextID, + Metadata: task.Metadata, + Status: status, + } + if len(task.History) > 0 { + result.History = make([]*legacya2a.Message, len(task.History)) + for i := range task.History { + result.History[i], err = ToOfficialV0Message(&task.History[i]) + if err != nil { + return nil, fmt.Errorf("convert task history message %d: %w", i, err) + } + } + } + if len(task.Artifacts) > 0 { + result.Artifacts = make([]*legacya2a.Artifact, len(task.Artifacts)) + for i := range task.Artifacts { + result.Artifacts[i], err = ToOfficialV0Artifact(&task.Artifacts[i]) + if err != nil { + return nil, fmt.Errorf("convert task artifact %d: %w", i, err) + } + } + } + return result, nil +} + +func toLegacyMessage(message *a2av1.Message) (*trpc.Message, error) { + if message == nil { + return nil, nil + } + parts, err := toLegacyParts(message.Parts) + if err != nil { + return nil, err + } + + taskID := "" + if message.TaskID != "" { + taskID = string(message.TaskID) + } + contextID := message.ContextID + + result := &trpc.Message{ + Kind: trpc.KindMessage, + MessageID: message.ID, + ContextID: strPtr(contextID), + Extensions: message.Extensions, + Metadata: message.Metadata, + Parts: parts, + ReferenceTaskIDs: toLegacyTaskIDs(message.ReferenceTasks), + Role: toLegacyMessageRole(message.Role), + TaskID: strPtr(taskID), + } + return result, nil +} + +func toLegacyArtifact(artifact *a2av1.Artifact) (*trpc.Artifact, error) { + if artifact == nil { + return nil, nil + } + parts, err := toLegacyParts(artifact.Parts) + if err != nil { + return nil, err + } + id := string(artifact.ID) + name := artifact.Name + description := artifact.Description + + return &trpc.Artifact{ + ArtifactID: id, + Name: strPtr(name), + Description: strPtr(description), + Metadata: artifact.Metadata, + Extensions: artifact.Extensions, + Parts: parts, + }, nil +} + +func toLegacyParts(parts a2av1.ContentParts) ([]trpc.Part, error) { + if len(parts) == 0 { + return nil, nil + } + result := make([]trpc.Part, 0, len(parts)) + for i := range parts { + part, err := toLegacyPart(parts[i]) + if err != nil { + return nil, fmt.Errorf("convert part %d: %w", i, err) + } + result = append(result, part) + } + return result, nil +} + +func toLegacyPart(part *a2av1.Part) (trpc.Part, error) { + if part == nil { + return trpc.TextPart{Kind: trpc.KindText, Text: ""}, nil + } + if text := part.Text(); text != "" { + return trpc.TextPart{ + Kind: trpc.KindText, + Text: text, + Metadata: part.Metadata, + }, nil + } + if data := part.Data(); data != nil { + return trpc.DataPart{ + Kind: trpc.KindData, + Data: data, + Metadata: part.Metadata, + }, nil + } + if url := part.URL(); url != "" { + urlString := string(url) + fileName := part.Filename + mimeType := part.MediaType + return trpc.FilePart{ + Kind: trpc.KindFile, + File: &trpc.FileWithURI{ + Name: strPtr(fileName), + MimeType: strPtr(mimeType), + URI: urlString, + }, + Metadata: part.Metadata, + }, nil + } + raw := part.Raw() + if len(raw) > 0 { + fileName := part.Filename + mimeType := part.MediaType + return trpc.FilePart{ + Kind: trpc.KindFile, + File: &trpc.FileWithBytes{ + Name: strPtr(fileName), + MimeType: strPtr(mimeType), + Bytes: string(raw), + }, + Metadata: part.Metadata, + }, nil + } + return trpc.DataPart{ + Kind: trpc.KindData, + Data: map[string]any{}, + Metadata: part.Metadata, + }, nil +} + +func toLegacyTaskState(state a2av1.TaskState) trpc.TaskState { + switch state { + case a2av1.TaskStateSubmitted: + return trpc.TaskStateSubmitted + case a2av1.TaskStateWorking: + return trpc.TaskStateWorking + case a2av1.TaskStateInputRequired: + return trpc.TaskStateInputRequired + case a2av1.TaskStateCompleted: + return trpc.TaskStateCompleted + case a2av1.TaskStateCanceled: + return trpc.TaskStateCanceled + case a2av1.TaskStateFailed: + return trpc.TaskStateFailed + case a2av1.TaskStateRejected: + return trpc.TaskStateRejected + case a2av1.TaskStateAuthRequired: + return trpc.TaskStateAuthRequired + default: + return trpc.TaskStateUnknown + } +} + +func toLegacyMessageRole(role a2av1.MessageRole) trpc.MessageRole { + switch role { + case a2av1.MessageRoleAgent: + return trpc.MessageRoleAgent + case a2av1.MessageRoleUser: + return trpc.MessageRoleUser + default: + return trpc.MessageRoleAgent + } +} + +func toLegacyTaskIDs(ids []a2av1.TaskID) []string { + if len(ids) == 0 { + return nil + } + result := make([]string, len(ids)) + for i := range ids { + result[i] = string(ids[i]) + } + return result +} + +func formatTimestamp(timestamp *time.Time) string { + if timestamp == nil { + return "" + } + return timestamp.UTC().Format(time.RFC3339Nano) +} + +func strPtr(s string) *string { + return &s +} + +func ToOfficialV0TaskStatus(status trpc.TaskStatus) (legacya2a.TaskStatus, error) { + var msg *legacya2a.Message + var err error + if status.Message != nil { + msg, err = ToOfficialV0Message(status.Message) + if err != nil { + return legacya2a.TaskStatus{}, fmt.Errorf("convert task status message: %w", err) + } + } + timestamp, err := parseTimestamp(status.Timestamp) + if err != nil { + return legacya2a.TaskStatus{}, err + } + return legacya2a.TaskStatus{ + Message: msg, + State: ToOfficialV0TaskState(status.State), + Timestamp: timestamp, + }, nil +} + +func ToOfficialV0Message(message *trpc.Message) (*legacya2a.Message, error) { + if message == nil { + return nil, nil + } + parts, err := ToOfficialV0Parts(message.Parts) + if err != nil { + return nil, err + } + return &legacya2a.Message{ + ID: message.MessageID, + ContextID: derefString(message.ContextID), + Extensions: message.Extensions, + Metadata: message.Metadata, + Parts: parts, + ReferenceTasks: toOfficialV0TaskIDs(message.ReferenceTaskIDs), + Role: ToOfficialV0MessageRole(message.Role), + TaskID: legacya2a.TaskID(derefString(message.TaskID)), + }, nil +} + +func ToOfficialV0Artifact(artifact *trpc.Artifact) (*legacya2a.Artifact, error) { + if artifact == nil { + return nil, nil + } + parts, err := ToOfficialV0Parts(artifact.Parts) + if err != nil { + return nil, err + } + return &legacya2a.Artifact{ + ID: legacya2a.ArtifactID(artifact.ArtifactID), + Description: derefString(artifact.Description), + Extensions: artifact.Extensions, + Metadata: artifact.Metadata, + Name: derefString(artifact.Name), + Parts: parts, + }, nil +} + +func ToOfficialV0Parts(parts []trpc.Part) (legacya2a.ContentParts, error) { + if len(parts) == 0 { + return nil, nil + } + result := make(legacya2a.ContentParts, len(parts)) + for i, part := range parts { + converted, err := ToOfficialV0Part(part) + if err != nil { + return nil, fmt.Errorf("convert part %d: %w", i, err) + } + result[i] = converted + } + return result, nil +} + +func ToOfficialV0Part(part trpc.Part) (legacya2a.Part, error) { + switch p := part.(type) { + case nil: + return nil, nil + case trpc.TextPart: + return legacya2a.TextPart{Text: p.Text, Metadata: p.Metadata}, nil + case *trpc.TextPart: + return legacya2a.TextPart{Text: p.Text, Metadata: p.Metadata}, nil + case trpc.DataPart: + return toOfficialV0DataPart(p), nil + case *trpc.DataPart: + return toOfficialV0DataPart(*p), nil + case trpc.FilePart: + return ToOfficialV0FilePart(p) + case *trpc.FilePart: + return ToOfficialV0FilePart(*p) + default: + return nil, fmt.Errorf("unsupported trpc part type %T", part) + } +} + +func ToOfficialV0FilePart(part trpc.FilePart) (legacya2a.Part, error) { + switch file := part.File.(type) { + case nil: + return nil, fmt.Errorf("file part missing file payload") + case *trpc.FileWithBytes: + return officialV0FileBytes(*file, part.Metadata), nil + case *trpc.FileWithURI: + return officialV0FileURI(*file, part.Metadata), nil + default: + return nil, fmt.Errorf("unsupported trpc file payload type %T", part.File) + } +} + +func ToOfficialV0PushConfig(cfg *trpc.TaskPushNotificationConfig) *legacya2a.TaskPushConfig { + if cfg == nil { + return nil + } + pushConfig := legacya2a.PushConfig{ + ID: cfg.PushNotificationConfig.ID, + Token: cfg.PushNotificationConfig.Token, + URL: cfg.PushNotificationConfig.URL, + } + if cfg.PushNotificationConfig.Authentication != nil { + pushConfig.Auth = &legacya2a.PushAuthInfo{ + Credentials: derefString(cfg.PushNotificationConfig.Authentication.Credentials), + Schemes: cfg.PushNotificationConfig.Authentication.Schemes, + } + } + return &legacya2a.TaskPushConfig{ + Config: pushConfig, + TaskID: legacya2a.TaskID(cfg.TaskID), + } +} + +func ToOfficialV0TaskState(state trpc.TaskState) legacya2a.TaskState { + switch state { + case trpc.TaskStateSubmitted: + return legacya2a.TaskStateSubmitted + case trpc.TaskStateWorking: + return legacya2a.TaskStateWorking + case trpc.TaskStateInputRequired: + return legacya2a.TaskStateInputRequired + case trpc.TaskStateCompleted: + return legacya2a.TaskStateCompleted + case trpc.TaskStateCanceled: + return legacya2a.TaskStateCanceled + case trpc.TaskStateFailed: + return legacya2a.TaskStateFailed + case trpc.TaskStateRejected: + return legacya2a.TaskStateRejected + case trpc.TaskStateAuthRequired: + return legacya2a.TaskStateAuthRequired + case trpc.TaskStateUnknown: + return legacya2a.TaskStateUnknown + default: + return legacya2a.TaskStateUnspecified + } +} + +func ToOfficialV0MessageRole(role trpc.MessageRole) legacya2a.MessageRole { + switch role { + case trpc.MessageRoleAgent: + return legacya2a.MessageRoleAgent + case trpc.MessageRoleUser: + return legacya2a.MessageRoleUser + default: + return legacya2a.MessageRoleUnspecified + } +} + +func toOfficialV0DataPart(part trpc.DataPart) legacya2a.DataPart { + data, ok := part.Data.(map[string]any) + metadata := maps.Clone(part.Metadata) + if !ok { + data = map[string]any{"value": part.Data} + if metadata == nil { + metadata = map[string]any{} + } + metadata["data_part_compat"] = true + } + return legacya2a.DataPart{Data: data, Metadata: metadata} +} + +func officialV0FileBytes(file trpc.FileWithBytes, metadata map[string]any) legacya2a.FilePart { + return legacya2a.FilePart{ + File: legacya2a.FileBytes{ + FileMeta: legacya2a.FileMeta{ + MimeType: derefString(file.MimeType), + Name: derefString(file.Name), + }, + Bytes: file.Bytes, + }, + Metadata: metadata, + } +} + +func officialV0FileURI(file trpc.FileWithURI, metadata map[string]any) legacya2a.FilePart { + return legacya2a.FilePart{ + File: legacya2a.FileURI{ + FileMeta: legacya2a.FileMeta{ + MimeType: derefString(file.MimeType), + Name: derefString(file.Name), + }, + URI: file.URI, + }, + Metadata: metadata, + } +} + +func parseTimestamp(raw string) (*time.Time, error) { + if raw == "" { + return nil, nil + } + parsed, err := time.Parse(time.RFC3339Nano, raw) + if err != nil { + return nil, fmt.Errorf("parse task status timestamp %q: %w", raw, err) + } + return &parsed, nil +} + +func toOfficialV0TaskIDs(ids []string) []legacya2a.TaskID { + if len(ids) == 0 { + return nil + } + result := make([]legacya2a.TaskID, len(ids)) + for i, id := range ids { + result[i] = legacya2a.TaskID(id) + } + return result +} + +func derefString(value *string) string { + if value == nil { + return "" + } + return *value +} diff --git a/go/core/pkg/a2acompat/trpcv0/convert_test.go b/go/core/pkg/a2acompat/trpcv0/convert_test.go new file mode 100644 index 0000000000..44576d41c4 --- /dev/null +++ b/go/core/pkg/a2acompat/trpcv0/convert_test.go @@ -0,0 +1,385 @@ +package trpcv0 + +import ( + "encoding/json" + "testing" + "time" + + a2av1 "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/google/go-cmp/cmp" + trpc "trpc.group/trpc-go/trpc-a2a-go/protocol" +) + +func TestTaskJSONToV1JSON_ClusterTextTask(t *testing.T) { + task := mustConvertTaskJSONToV1(t, buildLegacyTextTaskFixture()) + assertForwardTextTaskFixture(t, task) +} + +func TestTaskJSONToV1JSON_ClusterDataTask(t *testing.T) { + task := mustConvertTaskJSONToV1(t, buildLegacyDataTaskFixture()) + assertForwardDataTaskFixture(t, task) +} + +func TestPushNotificationJSONToV1JSON(t *testing.T) { + cfg := mustConvertPushNotificationJSONToV1(t, buildLegacyPushConfigFixture()) + + want := a2av1.PushConfig{ + TaskID: "task-1", + ID: "cfg-1", + URL: "https://callback.example", + Token: "tok", + Auth: &a2av1.PushAuthInfo{ + Credentials: "cred", + Scheme: "Bearer", + }, + } + if diff := cmp.Diff(want, cfg); diff != "" { + t.Fatalf("unexpected push config (-want +got):\n%s", diff) + } +} + +func TestToLegacyTask_FromV1RichFixture(t *testing.T) { + v1Task := buildV1RichTaskFixture() + got := mustConvertToLegacyTask(t, v1Task) + assertBackwardTaskFixture(t, got) +} + +func TestToLegacyPushConfig_FromV1(t *testing.T) { + got := ToLegacyPushConfig(buildV1PushConfigFixture()) + if got == nil { + t.Fatal("expected non-nil config") + } + if got.TaskID != "task-v1-rich" || got.PushNotificationConfig.ID != "cfg-v1" || got.PushNotificationConfig.URL != "https://callback.example/v1" || got.PushNotificationConfig.Token != "token-v1" { + t.Fatalf("unexpected legacy push config: %+v", got) + } + if got.PushNotificationConfig.Authentication == nil { + t.Fatal("expected authentication") + } + if got.PushNotificationConfig.Authentication.Credentials == nil || *got.PushNotificationConfig.Authentication.Credentials != "secret" { + t.Fatalf("credentials = %v", got.PushNotificationConfig.Authentication.Credentials) + } + if len(got.PushNotificationConfig.Authentication.Schemes) != 1 || got.PushNotificationConfig.Authentication.Schemes[0] != "Bearer" { + t.Fatalf("schemes = %+v", got.PushNotificationConfig.Authentication.Schemes) + } +} + +func mustConvertTaskJSONToV1(t *testing.T, fixture trpc.Task) a2av1.Task { + t.Helper() + input, err := json.Marshal(fixture) + if err != nil { + t.Fatalf("marshal legacy task fixture: %v", err) + } + data, err := TaskJSONToV1JSON(input) + if err != nil { + t.Fatalf("TaskJSONToV1JSON() error = %v", err) + } + var task a2av1.Task + if err := json.Unmarshal(data, &task); err != nil { + t.Fatalf("unmarshal v1 task: %v\njson: %s", err, data) + } + return task +} + +func mustConvertPushNotificationJSONToV1(t *testing.T, fixture trpc.TaskPushNotificationConfig) a2av1.PushConfig { + t.Helper() + input, err := json.Marshal(fixture) + if err != nil { + t.Fatalf("marshal legacy push fixture: %v", err) + } + data, err := PushNotificationJSONToV1JSON(input) + if err != nil { + t.Fatalf("PushNotificationJSONToV1JSON() error = %v", err) + } + var cfg a2av1.PushConfig + if err := json.Unmarshal(data, &cfg); err != nil { + t.Fatalf("unmarshal v1 push config: %v", err) + } + return cfg +} + +func mustConvertToLegacyTask(t *testing.T, fixture *a2av1.Task) *trpc.Task { + t.Helper() + got, err := ToLegacyTask(fixture) + if err != nil { + t.Fatalf("ToLegacyTask() error = %v", err) + } + return got +} + +func assertForwardTextTaskFixture(t *testing.T, task a2av1.Task) { + t.Helper() + if task.ID != "019d49ab-6830-763c-9db6-1b6359228c4c" { + t.Fatalf("task ID = %q", task.ID) + } + if task.Status.State != a2av1.TaskStateCompleted { + t.Fatalf("task state = %q", task.Status.State) + } + if got := task.History[0].Role; got != a2av1.MessageRoleUser { + t.Fatalf("history role = %q", got) + } + if got := task.History[0].Parts[0].Text(); got != "hi" { + t.Fatalf("history text part = %q", got) + } + if got := task.Artifacts[0].Parts[0].Text(); got != "Hello! How can I assist you with Kubernetes today?" { + t.Fatalf("artifact text part = %q", got) + } +} + +func assertForwardDataTaskFixture(t *testing.T, task a2av1.Task) { + t.Helper() + if task.Status.State != a2av1.TaskStateInputRequired { + t.Fatalf("task state = %q", task.Status.State) + } + if task.Status.Message == nil { + t.Fatal("expected status message") + } + dataPart, ok := task.Status.Message.Parts[0].Data().(map[string]any) + if !ok { + t.Fatalf("status message part data type = %T", task.Status.Message.Parts[0].Data()) + } + if got := dataPart["name"]; got != "adk_request_confirmation" { + t.Fatalf("status message data name = %v", got) + } +} + +func assertBackwardTaskFixture(t *testing.T, got *trpc.Task) { + t.Helper() + if got.ID != "task-v1-rich" { + t.Fatalf("task ID = %q", got.ID) + } + if got.ContextID != "ctx-bridge" { + t.Fatalf("context ID = %q", got.ContextID) + } + if got.Status.State != trpc.TaskStateWorking { + t.Fatalf("status state = %q", got.Status.State) + } + wantTS := time.Date(2026, time.January, 2, 3, 4, 5, 123456000, time.UTC).Format(time.RFC3339Nano) + if got.Status.Timestamp != wantTS { + t.Fatalf("status timestamp = %q", got.Status.Timestamp) + } + if got.Status.Message == nil { + t.Fatal("expected status message") + } + if got.Status.Message.MessageID == "" { + t.Fatal("expected non-empty status message ID") + } + if got.Status.Message.TaskID == nil || *got.Status.Message.TaskID != "task-v1-rich" { + t.Fatalf("status message task ID = %v", got.Status.Message.TaskID) + } + + if len(got.History) != 1 { + t.Fatalf("history length = %d", len(got.History)) + } + if len(got.History[0].Parts) != 4 { + t.Fatalf("history parts length = %d", len(got.History[0].Parts)) + } + + textPart := mustTextPart(t, got.History[0].Parts[0]) + if textPart.Kind != trpc.KindText || textPart.Text != "hello" { + t.Fatalf("unexpected text part: %+v", textPart) + } + dataPart := mustDataPart(t, got.History[0].Parts[1]) + dataMap, ok := dataPart.Data.(map[string]any) + if !ok { + t.Fatalf("data part payload type = %T", dataPart.Data) + } + if dataMap["step"] != float64(1) { + t.Fatalf("data part step = %v", dataMap["step"]) + } + urlFilePart := mustFilePart(t, got.History[0].Parts[2]) + urlFile, ok := urlFilePart.File.(*trpc.FileWithURI) + if !ok { + t.Fatalf("expected FileWithURI, got %T", urlFilePart.File) + } + if urlFile.URI != "https://example.com/doc.md" { + t.Fatalf("file URI = %q", urlFile.URI) + } + rawFilePart := mustFilePart(t, got.History[0].Parts[3]) + rawFile, ok := rawFilePart.File.(*trpc.FileWithBytes) + if !ok { + t.Fatalf("expected FileWithBytes, got %T", rawFilePart.File) + } + if rawFile.Bytes != "RAW_BYTES" { + t.Fatalf("raw bytes = %q", rawFile.Bytes) + } + + if len(got.Artifacts) != 1 || len(got.Artifacts[0].Parts) != 1 { + t.Fatalf("unexpected artifacts: %+v", got.Artifacts) + } + if gotArtifactText := mustTextPart(t, got.Artifacts[0].Parts[0]).Text; gotArtifactText != "artifact-text" { + t.Fatalf("artifact text = %q", gotArtifactText) + } +} + +func mustTextPart(t *testing.T, part trpc.Part) trpc.TextPart { + t.Helper() + switch p := part.(type) { + case trpc.TextPart: + return p + case *trpc.TextPart: + return *p + default: + t.Fatalf("expected TextPart, got %T", part) + return trpc.TextPart{} + } +} + +func mustDataPart(t *testing.T, part trpc.Part) trpc.DataPart { + t.Helper() + switch p := part.(type) { + case trpc.DataPart: + return p + case *trpc.DataPart: + return *p + default: + t.Fatalf("expected DataPart, got %T", part) + return trpc.DataPart{} + } +} + +func mustFilePart(t *testing.T, part trpc.Part) trpc.FilePart { + t.Helper() + switch p := part.(type) { + case trpc.FilePart: + return p + case *trpc.FilePart: + return *p + default: + t.Fatalf("expected FilePart, got %T", part) + return trpc.FilePart{} + } +} + +func buildLegacyTextTaskFixture() trpc.Task { + return trpc.Task{ + ID: "019d49ab-6830-763c-9db6-1b6359228c4c", + Kind: trpc.KindTask, + ContextID: "ctx-text", + Status: trpc.TaskStatus{ + State: trpc.TaskStateCompleted, + Message: &trpc.Message{ + Kind: trpc.KindMessage, + MessageID: "msg-status-1", + Role: trpc.MessageRoleAgent, + Parts: []trpc.Part{ + trpc.TextPart{Kind: trpc.KindText, Text: "done"}, + }, + }, + }, + History: []trpc.Message{ + { + Kind: trpc.KindMessage, + MessageID: "msg-user-1", + Role: trpc.MessageRoleUser, + Parts: []trpc.Part{ + trpc.TextPart{Kind: trpc.KindText, Text: "hi"}, + }, + }, + }, + Artifacts: []trpc.Artifact{ + { + ArtifactID: "artifact-1", + Parts: []trpc.Part{ + trpc.TextPart{Kind: trpc.KindText, Text: "Hello! How can I assist you with Kubernetes today?"}, + }, + }, + }, + } +} + +func buildLegacyDataTaskFixture() trpc.Task { + return trpc.Task{ + ID: "task-data-1", + Kind: trpc.KindTask, + ContextID: "ctx-data", + Status: trpc.TaskStatus{ + State: trpc.TaskStateInputRequired, + Message: &trpc.Message{ + Kind: trpc.KindMessage, + MessageID: "msg-status-data-1", + Role: trpc.MessageRoleAgent, + Parts: []trpc.Part{ + trpc.DataPart{ + Kind: trpc.KindData, + Data: map[string]any{ + "name": "adk_request_confirmation", + }, + }, + }, + }, + }, + } +} + +func buildLegacyPushConfigFixture() trpc.TaskPushNotificationConfig { + cred := "cred" + return trpc.TaskPushNotificationConfig{ + TaskID: "task-1", + PushNotificationConfig: trpc.PushNotificationConfig{ + ID: "cfg-1", + URL: "https://callback.example", + Token: "tok", + Authentication: &trpc.AuthenticationInfo{ + Credentials: &cred, + Schemes: []string{"Bearer"}, + }, + }, + } +} + +func buildV1RichTaskFixture() *a2av1.Task { + ts := time.Date(2026, time.January, 2, 3, 4, 5, 123456000, time.UTC) + taskID := a2av1.TaskID("task-v1-rich") + fileURLPart := a2av1.NewFileURLPart(a2av1.URL("https://example.com/doc.md"), "text/markdown") + fileURLPart.Filename = "doc.md" + fileURLPart.Metadata = map[string]any{"source": "url"} + rawPart := a2av1.NewRawPart([]byte("RAW_BYTES")) + rawPart.Filename = "blob.bin" + rawPart.MediaType = "application/octet-stream" + rawPart.Metadata = map[string]any{"source": "raw"} + + statusMessage := a2av1.NewMessage(a2av1.MessageRoleAgent, a2av1.NewTextPart("working")) + statusMessage.TaskID = taskID + statusMessage.ContextID = "ctx-bridge" + + historyMessage := a2av1.NewMessage( + a2av1.MessageRoleUser, + a2av1.NewTextPart("hello"), + a2av1.NewDataPart(map[string]any{"step": float64(1)}), + fileURLPart, + rawPart, + ) + historyMessage.TaskID = taskID + historyMessage.ContextID = "ctx-bridge" + + return &a2av1.Task{ + ID: taskID, + ContextID: "ctx-bridge", + Metadata: map[string]any{"kagent": "true"}, + Status: a2av1.TaskStatus{ + State: a2av1.TaskStateWorking, + Timestamp: &ts, + Message: statusMessage, + }, + History: []*a2av1.Message{historyMessage}, + Artifacts: []*a2av1.Artifact{ + { + ID: "artifact-v1", + Parts: a2av1.ContentParts{a2av1.NewTextPart("artifact-text")}, + }, + }, + } +} + +func buildV1PushConfigFixture() *a2av1.PushConfig { + return &a2av1.PushConfig{ + TaskID: "task-v1-rich", + ID: "cfg-v1", + URL: "https://callback.example/v1", + Token: "token-v1", + Auth: &a2av1.PushAuthInfo{ + Credentials: "secret", + Scheme: "Bearer", + }, + } +} diff --git a/go/core/pkg/env/kagent.go b/go/core/pkg/env/kagent.go index 5d158b2060..be8c62d417 100644 --- a/go/core/pkg/env/kagent.go +++ b/go/core/pkg/env/kagent.go @@ -23,16 +23,6 @@ var ( ComponentController, ) - KagentMCPStateless = RegisterBoolVar( - "KAGENT_MCP_STATELESS", - false, - "When true, the MCP server operates in stateless mode (no session persistence). "+ - "Use when the network path does not provide sticky session routing based on the Mcp-Session-Id header. "+ - "Note: stateless mode disables server-initiated notifications; clients will not receive "+ - "resources/updated events.", - ComponentController, - ) - // Variables injected into agent pods (not read by the controller itself). KagentName = RegisterStringVar( diff --git a/go/core/pkg/migrations/core/000005_a2a_protocol_version.down.sql b/go/core/pkg/migrations/core/000005_a2a_protocol_version.down.sql new file mode 100644 index 0000000000..ad866c8d26 --- /dev/null +++ b/go/core/pkg/migrations/core/000005_a2a_protocol_version.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE task DROP COLUMN IF EXISTS protocol_version; +ALTER TABLE push_notification DROP COLUMN IF EXISTS protocol_version; diff --git a/go/core/pkg/migrations/core/000005_a2a_protocol_version.up.sql b/go/core/pkg/migrations/core/000005_a2a_protocol_version.up.sql new file mode 100644 index 0000000000..123820c133 --- /dev/null +++ b/go/core/pkg/migrations/core/000005_a2a_protocol_version.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE task ADD COLUMN IF NOT EXISTS protocol_version TEXT; +ALTER TABLE push_notification ADD COLUMN IF NOT EXISTS protocol_version TEXT; diff --git a/go/core/pkg/translator/adk_api_translator_types.go b/go/core/pkg/translator/adk_api_translator_types.go index 07fd4268d5..df07f70546 100644 --- a/go/core/pkg/translator/adk_api_translator_types.go +++ b/go/core/pkg/translator/adk_api_translator_types.go @@ -3,17 +3,17 @@ package translator import ( "context" + a2a "github.com/a2aproject/a2a-go/v2/a2a" "github.com/kagent-dev/kagent/go/api/adk" "github.com/kagent-dev/kagent/go/api/v1alpha2" "sigs.k8s.io/controller-runtime/pkg/client" - "trpc.group/trpc-go/trpc-a2a-go/server" ) type AgentOutputs struct { Manifest []client.Object `json:"manifest,omitempty"` Config *adk.AgentConfig `json:"config,omitempty"` - AgentCard server.AgentCard `json:"agentCard"` + AgentCard a2a.AgentCard `json:"agentCard"` } type TranslatorPlugin interface { diff --git a/go/core/test/e2e/invoke_api_test.go b/go/core/test/e2e/invoke_api_test.go index 528fce2882..271eff8db7 100644 --- a/go/core/test/e2e/invoke_api_test.go +++ b/go/core/test/e2e/invoke_api_test.go @@ -21,6 +21,9 @@ import ( "k8s.io/client-go/util/retry" "sigs.k8s.io/controller-runtime/pkg/client" + a2atype "github.com/a2aproject/a2a-go/v2/a2a" + a2aclient "github.com/a2aproject/a2a-go/v2/a2aclient" + "github.com/a2aproject/a2a-go/v2/a2aclient/agentcard" "github.com/kagent-dev/kagent/go/api/v1alpha2" "github.com/kagent-dev/kagent/go/core/internal/a2a" "github.com/kagent-dev/kagent/go/core/internal/utils" @@ -29,8 +32,6 @@ import ( "github.com/kagent-dev/mockllm" "github.com/stretchr/testify/require" "sigs.k8s.io/controller-runtime/pkg/client/config" - a2aclient "trpc.group/trpc-go/trpc-a2a-go/client" - "trpc.group/trpc-go/trpc-a2a-go/protocol" ) //go:embed mocks @@ -231,28 +232,55 @@ func setupSandboxAgentWithOptions(t *testing.T, cli client.Client, modelConfigNa return agent } -// setupA2AClient creates an A2A client for the test agent -func setupA2AClient(t *testing.T, agent *v1alpha2.Agent) *a2aclient.A2AClient { - a2aURL := a2aURL(agent.Namespace, agent.Name, false) - a2aClient, err := a2aclient.NewA2AClient(a2aURL) +func newA2AClient(t *testing.T, baseURL string, httpClient *http.Client, headers map[string]string) *a2aclient.Client { + t.Helper() + if httpClient == nil { + httpClient = &http.Client{Timeout: 60 * time.Second} + } + if headers == nil { + headers = map[string]string{} + } + if _, ok := headers["A2A-Version"]; !ok { + headers["A2A-Version"] = string(a2atype.Version) + } + + resolver := agentcard.NewResolver(httpClient) + resolveOpts := make([]agentcard.ResolveOption, 0, len(headers)) + for k, v := range headers { + resolveOpts = append(resolveOpts, agentcard.WithRequestHeader(k, v)) + } + + card, err := resolver.Resolve(t.Context(), baseURL, resolveOpts...) + require.NoError(t, err) + + a2aClient, err := a2aclient.NewFromCard( + t.Context(), + card, + a2aclient.WithJSONRPCTransport(httpClient), + a2aclient.WithCallInterceptors(a2a.NewStaticHeadersInterceptor(headers)), + ) require.NoError(t, err) + return a2aClient } +// setupA2AClient creates an A2A client for the test agent +func setupA2AClient(t *testing.T, agent *v1alpha2.Agent) *a2aclient.Client { + return newA2AClient(t, a2aURL(agent.Namespace, agent.Name, false), nil, nil) +} + // setupSandboxA2AClient creates an A2A client for the test sandbox agent. -func setupSandboxA2AClient(t *testing.T, agent *v1alpha2.SandboxAgent) *a2aclient.A2AClient { - a2aClient, err := a2aclient.NewA2AClient(a2aURL(agent.Namespace, agent.Name, true)) - require.NoError(t, err) - return a2aClient +func setupSandboxA2AClient(t *testing.T, agent *v1alpha2.SandboxAgent) *a2aclient.Client { + return newA2AClient(t, a2aURL(agent.Namespace, agent.Name, true), nil, nil) } // extractTextFromArtifacts extracts all text content from task artifacts -func extractTextFromArtifacts(taskResult *protocol.Task) string { +func extractTextFromArtifacts(taskResult *a2atype.Task) string { var text strings.Builder for _, artifact := range taskResult.Artifacts { for _, part := range artifact.Parts { - if textPart, ok := part.(*protocol.TextPart); ok { - text.WriteString(textPart.Text) + if part != nil { + text.WriteString(part.Text()) } } } @@ -269,22 +297,18 @@ var defaultRetry = wait.Backoff{ // runSyncTest runs a synchronous message test // useArtifacts: if true, check artifacts; if false or nil, check history; // contextID: optional context ID to maintain conversation context -func runSyncTest(t *testing.T, a2aClient *a2aclient.A2AClient, userMessage, expectedText string, useArtifacts *bool, contextID ...string) *protocol.Task { +func runSyncTest(t *testing.T, a2aClient *a2aclient.Client, userMessage, expectedText string, useArtifacts *bool, contextID ...string) *a2atype.Task { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - msg := protocol.Message{ - Kind: protocol.KindMessage, - Role: protocol.MessageRoleUser, - Parts: []protocol.Part{protocol.NewTextPart(userMessage)}, - } + msg := a2atype.NewMessage(a2atype.MessageRoleUser, a2atype.NewTextPart(userMessage)) // If contextID is provided, set it to maintain conversation context if len(contextID) > 0 && contextID[0] != "" { - msg.ContextID = &contextID[0] + msg.ContextID = contextID[0] } - var result *protocol.MessageResult + var result a2atype.SendMessageResult err := retry.OnError(defaultRetry, func(err error) bool { return err != nil }, func() error { @@ -294,13 +318,13 @@ func runSyncTest(t *testing.T, a2aClient *a2aclient.A2AClient, userMessage, expe ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() t.Logf("%s trying to send message", time.Now().Format(time.RFC3339)) - result, retryErr = a2aClient.SendMessage(ctx, protocol.SendMessageParams{Message: msg}) + result, retryErr = a2aClient.SendMessage(ctx, &a2atype.SendMessageRequest{Message: msg}) t.Logf("%s finished trying sending message. success = %v", time.Now().Format(time.RFC3339), retryErr == nil) return retryErr }) require.NoError(t, err) - taskResult, ok := result.Result.(*protocol.Task) + taskResult, ok := result.(*a2atype.Task) require.True(t, ok) // Extract text based on useArtifacts flag @@ -322,22 +346,18 @@ func runSyncTest(t *testing.T, a2aClient *a2aclient.A2AClient, userMessage, expe // runStreamingTest runs a streaming message test // If contextID is provided, it will be included in the message to maintain conversation context // Checks the full JSON output to support both artifacts and history from different agent types -func runStreamingTest(t *testing.T, a2aClient *a2aclient.A2AClient, userMessage, expectedText string, contextID ...string) { - msg := protocol.Message{ - Kind: protocol.KindMessage, - Role: protocol.MessageRoleUser, - Parts: []protocol.Part{protocol.NewTextPart(userMessage)}, - } +func runStreamingTest(t *testing.T, a2aClient *a2aclient.Client, userMessage, expectedText string, contextID ...string) { + msg := a2atype.NewMessage(a2atype.MessageRoleUser, a2atype.NewTextPart(userMessage)) // If contextID is provided, set it to maintain conversation context if len(contextID) > 0 && contextID[0] != "" { - msg.ContextID = &contextID[0] + msg.ContextID = contextID[0] } // Retry the entire stream-connect-read-check cycle. // The most common failure mode is: stream connects but yields zero events // (agent not ready, stream closes early), so we need to retry the whole operation. - var lastJSON string + var lastText string err := retry.OnError(defaultRetry, func(err error) bool { return err != nil }, func() error { @@ -345,35 +365,51 @@ func runStreamingTest(t *testing.T, a2aClient *a2aclient.A2AClient, userMessage, defer cancel() t.Logf("%s trying to open stream", time.Now().Format(time.RFC3339)) - stream, streamErr := a2aClient.StreamMessage(ctx, protocol.SendMessageParams{Message: msg}) - if streamErr != nil { - t.Logf("%s stream connection failed: %v", time.Now().Format(time.RFC3339), streamErr) - return streamErr - } - - resultList := []protocol.StreamingMessageEvent{} - for event := range stream { - if _, ok := event.Result.(*protocol.TaskStatusUpdateEvent); !ok { + stream := a2aClient.SendStreamingMessage(ctx, &a2atype.SendMessageRequest{Message: msg}) + texts := make([]string, 0) + eventCount := 0 + for event, streamErr := range stream { + if streamErr != nil { + t.Logf("%s stream read failed: %v", time.Now().Format(time.RFC3339), streamErr) + return streamErr + } + eventCount++ + if event == nil { continue } - resultList = append(resultList, event) - } - - jsn, marshalErr := json.Marshal(resultList) - if marshalErr != nil { - return marshalErr + texts = append(texts, extractTextFromEvent(event)) } - lastJSON = string(jsn) + lastText = strings.Join(texts, "\n") - if !strings.Contains(lastJSON, expectedText) { - t.Logf("%s stream completed but expected text %q not found in response (got %d events)", time.Now().Format(time.RFC3339), expectedText, len(resultList)) - return fmt.Errorf("expected text %q not found in streaming response (%d events)", expectedText, len(resultList)) + if !strings.Contains(lastText, expectedText) { + t.Logf("%s stream completed but expected text %q not found in response (got %d events)", time.Now().Format(time.RFC3339), expectedText, eventCount) + return fmt.Errorf("expected text %q not found in streaming response (%d events)", expectedText, eventCount) } - t.Logf("%s stream completed successfully with %d events", time.Now().Format(time.RFC3339), len(resultList)) + t.Logf("%s stream completed successfully with %d events", time.Now().Format(time.RFC3339), eventCount) return nil }) - require.NoError(t, err, lastJSON) + require.NoError(t, err, lastText) +} + +func extractTextFromEvent(event a2atype.Event) string { + switch e := event.(type) { + case *a2atype.TaskStatusUpdateEvent: + return a2a.ExtractText(e.Status.Message) + case *a2atype.TaskArtifactUpdateEvent: + return a2a.ExtractText(&a2atype.Message{Parts: e.Artifact.Parts}) + case *a2atype.Message: + return a2a.ExtractText(e) + case *a2atype.Task: + text := strings.Builder{} + if e.Status.Message != nil { + text.WriteString(a2a.ExtractText(e.Status.Message)) + } + text.WriteString(extractTextFromArtifacts(e)) + return text.String() + default: + return "" + } } func a2aURL(namespace, name string, sandbox bool) string { @@ -660,7 +696,7 @@ func TestE2EInvokeSandboxAgent(t *testing.T) { agent := setupSandboxAgentWithOptions(t, cli, modelCfg.Name, tools, AgentOptions{Stream: true}) a2aClient := setupSandboxA2AClient(t, agent) - var taskResult *protocol.Task + var taskResult *a2atype.Task t.Run("sync_invocation", func(t *testing.T) { taskResult = runSyncTest(t, a2aClient, "List all nodes in the cluster", "kagent-control-plane", nil) @@ -674,8 +710,7 @@ func TestE2EInvokeSandboxAgent(t *testing.T) { func TestE2EInvokeExternalAgent(t *testing.T) { // Setup A2A client for external agent a2aURL := a2aUrl("kagent", "kebab-agent") - a2aClient, err := a2aclient.NewA2AClient(a2aURL) - require.NoError(t, err) + a2aClient := newA2AClient(t, a2aURL, nil, nil) // Run tests t.Run("sync_invocation", func(t *testing.T) { @@ -688,8 +723,7 @@ func TestE2EInvokeExternalAgent(t *testing.T) { t.Run("invocation with different user", func(t *testing.T) { // Setup A2A client with authentication - authClient, err := a2aclient.NewA2AClient(a2aURL, a2aclient.WithAPIKeyAuth("user@example.com", "x-user-id")) - require.NoError(t, err) + authClient := newA2AClient(t, a2aURL, nil, map[string]string{"x-user-id": "user@example.com"}) runSyncTest(t, authClient, "What can you do?", "kebab for user@example.com", nil) }) @@ -887,8 +921,7 @@ func TestE2EInvokeOpenAIAgent(t *testing.T) { // Setup A2A client - use the agent's actual name a2aURL := a2aUrl("kagent", "basic-openai-test-agent") - a2aClient, err := a2aclient.NewA2AClient(a2aURL) - require.NoError(t, err) + a2aClient := newA2AClient(t, a2aURL, nil, nil) useArtifacts := true t.Run("sync_invocation_calculator", func(t *testing.T) { @@ -950,8 +983,7 @@ func TestE2EInvokeLangGraphAgent(t *testing.T) { // Setup A2A client a2aURL := a2aUrl(agent.Namespace, agent.Name) - a2aClient, err := a2aclient.NewA2AClient(a2aURL) - require.NoError(t, err) + a2aClient := newA2AClient(t, a2aURL, nil, nil) t.Run("sync_invocation", func(t *testing.T) { runSyncTest(t, a2aClient, "make me a kebab", "kebab is ready", nil) @@ -1024,8 +1056,7 @@ func TestE2EInvokeCrewAIAgent(t *testing.T) { // Setup A2A client a2aURL := a2aUrl(agent.Namespace, agent.Name) - a2aClient, err := a2aclient.NewA2AClient(a2aURL) - require.NoError(t, err) + a2aClient := newA2AClient(t, a2aURL, nil, nil) t.Run("two_turn_conversation", func(t *testing.T) { // First turn: Generate initial poem @@ -1044,15 +1075,6 @@ func TestE2EInvokeCrewAIAgent(t *testing.T) { } func TestE2EInvokeSTSIntegration(t *testing.T) { - runE2EInvokeSTSIntegration(t, "python", nil) -} - -func TestE2EGoInvokeSTSIntegration(t *testing.T) { - goRuntime := v1alpha2.DeclarativeRuntime_Go - runE2EInvokeSTSIntegration(t, "go", &goRuntime) -} - -func runE2EInvokeSTSIntegration(t *testing.T, runtimeName string, runtimeOverride *v1alpha2.DeclarativeRuntime) { // Setup mock STS server agentName := "test-sts" agentServiceAccount := fmt.Sprintf("system:serviceaccount:kagent:%s", agentName) @@ -1088,9 +1110,8 @@ func runE2EInvokeSTSIntegration(t *testing.T, runtimeName string, runtimeOverrid modelCfg := setupModelConfig(t, cli, baseURL) agent := setupAgentWithOptions(t, cli, modelCfg.Name, tools, AgentOptions{ - Name: "test-sts-agent-" + runtimeName, + Name: "test-sts-agent", SystemMessage: "You are an agent that adds numbers using the add tool available to you through the everything-mcp-server.", - Runtime: runtimeOverride, Env: []corev1.EnvVar{ { Name: "STS_WELL_KNOWN_URI", @@ -1116,12 +1137,9 @@ func runE2EInvokeSTSIntegration(t *testing.T, runtimeName string, runtimeOverrid } a2aURL := a2aUrl(agent.Namespace, agent.Name) - a2aClient, err := a2aclient.NewA2AClient(a2aURL, - a2aclient.WithTimeout(60*time.Second), - a2aclient.WithHTTPClient(httpClient)) - require.NoError(t, err) + a2aClient := newA2AClient(t, a2aURL, httpClient, nil) - t.Run(runtimeName+"/sts_exchange_sync_invocation", func(t *testing.T) { + t.Run("sync_invocation", func(t *testing.T) { runSyncTest(t, a2aClient, "add 3 and 5", "8", nil) // verify our mock STS server received the token exchange request @@ -1132,10 +1150,6 @@ func runE2EInvokeSTSIntegration(t *testing.T, runtimeName string, runtimeOverrid // which contains the may act claim stsRequest := stsRequests[0] require.Equal(t, subjectToken, stsRequest.SubjectToken) - require.Equal(t, "urn:ietf:params:oauth:grant-type:token-exchange", stsRequest.GrantType) - require.Equal(t, "urn:ietf:params:oauth:token-type:jwt", stsRequest.SubjectTokenType) - require.NotEmpty(t, stsRequest.ActorToken) - require.Equal(t, "urn:ietf:params:oauth:token-type:jwt", stsRequest.ActorTokenType) }) } @@ -1324,10 +1338,7 @@ func TestE2EInvokePassthroughAgent(t *testing.T) { }, } a2aURL := a2aUrl(agent.Namespace, agent.Name) - a2aClient, err := a2aclient.NewA2AClient(a2aURL, - a2aclient.WithTimeout(60*time.Second), - a2aclient.WithHTTPClient(httpClient)) - require.NoError(t, err) + a2aClient := newA2AClient(t, a2aURL, httpClient, nil) // The mock server will only match if it receives the exact // Authorization header "Bearer passthrough-test-token-12345". @@ -1390,7 +1401,7 @@ func runMemoryAgentTest(t *testing.T, extraOpts AgentOptions) { agent := setupAgentWithOptions(t, cli, llmModelCfg.Name, nil, opts) a2aClient := setupA2AClient(t, agent) - var saveResult *protocol.Task + var saveResult *a2atype.Task t.Run("save_memory", func(t *testing.T) { saveResult = runSyncTest(t, a2aClient, "Remember that I prefer dark mode and Go over Python", diff --git a/go/go.mod b/go/go.mod index 1b498ee8d8..feb643b0a6 100644 --- a/go/go.mod +++ b/go/go.mod @@ -8,6 +8,7 @@ require ( // adk dependencies github.com/a2aproject/a2a-go v0.3.15 + github.com/a2aproject/a2a-go/v2 v2.3.1 github.com/abiosoft/ishell/v2 v2.0.2 github.com/anthropics/anthropic-sdk-go v1.43.0 github.com/aws/aws-sdk-go-v2/config v1.32.17 @@ -18,7 +19,6 @@ require ( github.com/fatih/color v1.19.0 github.com/go-logr/logr v1.4.3 github.com/go-logr/zapr v1.3.0 - github.com/golang-jwt/jwt/v5 v5.3.1 github.com/golang-migrate/migrate/v4 v4.19.1 // api dependencies github.com/google/uuid v1.6.0 @@ -47,7 +47,7 @@ require ( go.uber.org/goleak v1.3.0 go.uber.org/zap v1.28.0 golang.org/x/text v0.37.0 - google.golang.org/adk v1.2.0 + google.golang.org/adk v1.3.0 google.golang.org/genai v1.57.0 google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af gopkg.in/yaml.v3 v3.0.1 @@ -64,6 +64,7 @@ require ( github.com/aws/aws-sdk-go-v2 v1.41.7 github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.6 github.com/golang/protobuf v1.5.4 + github.com/google/go-cmp v0.7.0 github.com/google/jsonschema-go v0.4.3 github.com/jackc/pgx/v5 v5.9.2 github.com/ollama/ollama v0.24.0 @@ -85,7 +86,7 @@ require ( cel.dev/expr v0.25.1 // indirect charm.land/lipgloss/v2 v2.0.3 // indirect cloud.google.com/go v0.123.0 // indirect - cloud.google.com/go/auth v0.18.2 // indirect + cloud.google.com/go/auth v0.20.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect codeberg.org/chavacava/garif v0.2.0 // indirect @@ -222,6 +223,7 @@ require ( github.com/goccy/go-json v0.10.3 // indirect github.com/godoc-lint/godoc-lint v0.11.2 // indirect github.com/gofrs/flock v0.13.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golangci/asciicheck v0.5.0 // indirect github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 // indirect github.com/golangci/go-printf-func-name v0.1.1 // indirect @@ -236,11 +238,10 @@ require ( github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect github.com/google/cel-go v0.26.0 // indirect github.com/google/gnostic-models v0.7.0 // indirect - github.com/google/go-cmp v0.7.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/safehtml v0.1.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect - github.com/googleapis/gax-go/v2 v2.18.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect + github.com/googleapis/gax-go/v2 v2.22.0 // indirect github.com/gordonklaus/ineffassign v0.2.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.5.0 // indirect @@ -391,7 +392,7 @@ require ( go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.42.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect @@ -417,9 +418,9 @@ require ( golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.45.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/api v0.272.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect + google.golang.org/api v0.279.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260427160629-7cedc36a6bc4 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect honnef.co/go/tools v0.7.0 // indirect diff --git a/go/go.sum b/go/go.sum index 7392185179..096e85b5e0 100644 --- a/go/go.sum +++ b/go/go.sum @@ -8,8 +8,8 @@ charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU= charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= -cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= -cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= +cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA= +cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= @@ -64,6 +64,8 @@ github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsu github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= github.com/a2aproject/a2a-go v0.3.15 h1:h5YpCiPq3jxQ5rIns7oDjPag3ivP8u817AzdA4F+NiI= github.com/a2aproject/a2a-go v0.3.15/go.mod h1:I7Cm+a1oL+UT6zMoP+roaRE5vdfUa1iQGVN8aSOuZ0I= +github.com/a2aproject/a2a-go/v2 v2.3.1 h1:QWMdOX2UsJ8BJmjs952eo1FRyGsOVl0gFCKeM76AgGE= +github.com/a2aproject/a2a-go/v2 v2.3.1/go.mod h1:mkZr8y2bUgAVQsjs/5fHK7xrRlAHDybMEyxWh2tKRC8= github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw= github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg= github.com/abiosoft/ishell/v2 v2.0.2 h1:5qVfGiQISaYM8TkbBl7RFO6MddABoXpATrsFbVI+SNo= @@ -424,10 +426,10 @@ github.com/google/safehtml v0.1.0 h1:EwLKo8qawTKfsi0orxcQAZzu07cICaBeFMegAU9eaT8 github.com/google/safehtml v0.1.0/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8= -github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= -github.com/googleapis/gax-go/v2 v2.18.0 h1:jxP5Uuo3bxm3M6gGtV94P4lliVetoCB4Wk2x8QA86LI= -github.com/googleapis/gax-go/v2 v2.18.0/go.mod h1:uSzZN4a356eRG985CzJ3WfbFSpqkLTjsnhWGJR6EwrE= +github.com/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas= +github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= +github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4= +github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY= github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs= github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -869,8 +871,8 @@ go.opentelemetry.io/contrib/detectors/gcp v1.42.0 h1:kpt2PEJuOuqYkPcktfJqWWDjTEd go.opentelemetry.io/contrib/detectors/gcp v1.42.0/go.mod h1:W9zQ439utxymRrXsUOzZbFX4JhLxXU4+ZnCt8GG7yA8= go.opentelemetry.io/contrib/exporters/autoexport v0.68.0 h1:0D3GFvELGIwQGfC6agLsbrEYSGWZTRTxIXxcQUqrOuk= go.opentelemetry.io/contrib/exporters/autoexport v0.68.0/go.mod h1:DM2NV7Zb8CcGeVPt6glouY0FAiwZQ/iqgcWExhgWeN8= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 h1:XmiuHzgJt067+a6kwyAzkhXooYVv3/TOw9cM2VfJgUM= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0/go.mod h1:KDgtbWKTQs4bM+VPUr6WlL9m/WXcmkCcBlIzqxPGzmI= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= @@ -1025,16 +1027,18 @@ gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0 gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/adk v1.2.0 h1:MfQD1/GqPfIsFNBcozNykkjdqNIdCrPH/SNqKPZF/yM= -google.golang.org/adk v1.2.0/go.mod h1:6QY5jQI7awU4WYtJqvyIkJQheCvqsGWweU6BX63USEc= -google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA= -google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA= +google.golang.org/adk v1.3.0 h1:paUr9uM2qANnMUAQ4ydMXMCnM1HtymhDYl8y7gnKvqs= +google.golang.org/adk v1.3.0/go.mod h1:R8tNFnI/eiBXHn7zJPJtqdiK/WXC+tVkyuZsXyNZXN4= +google.golang.org/api v0.279.0 h1:hsx2M2OaRcaKtVYK6vXEUnQvdjnend7ZYES+lYaot74= +google.golang.org/api v0.279.0/go.mod h1:B9TqLBwJqVjp1mtt7WeoQwWRwvu/400y5lETOql+giQ= google.golang.org/genai v1.57.0 h1:qTyG2ynz5dQy2jF4CvZdLHHVslhR0heMue+zM1a4GNM= google.golang.org/genai v1.57.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk= -google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d h1:/aDRtSZJjyLQzm75d+a1wOJaqyKBMvIAfeQmoa3ORiI= -google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:etfGUgejTiadZAUaEP14NP97xi1RGeawqkjDARA/UOs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= +google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= +google.golang.org/genproto/googleapis/api v0.0.0-20260427160629-7cedc36a6bc4 h1:yOzSCGPx+cp5VO7IxvZ9SBFF7j1tZVcNtlHR2iYKtVo= +google.golang.org/genproto/googleapis/api v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:Q9HWtNeE7tM9npdIsEvqXj1QJIvVoeAV3rtXtS715Cw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 h1:seT2EwLWM78plQ7wcDfuWBc/4FAEAXDDiaSol4ku4qo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= From 19e12450991ffba5398f044169a57ba60fe56ec4 Mon Sep 17 00:00:00 2001 From: Jet Chiang Date: Mon, 25 May 2026 13:20:51 -0400 Subject: [PATCH 2/8] some fixes Signed-off-by: Jet Chiang --- go/core/internal/a2a/a2a_handler_mux.go | 18 +++++++++-- go/core/internal/a2a/a2a_registrar.go | 12 ++++++++ .../httpserver/handlers/a2a_version.go | 29 ------------------ .../internal/httpserver/handlers/sessions.go | 6 ++-- go/core/internal/httpserver/handlers/tasks.go | 17 ++++++----- go/core/internal/utils/a2a_version.go | 30 +++++++++++++++++++ 6 files changed, 70 insertions(+), 42 deletions(-) delete mode 100644 go/core/internal/httpserver/handlers/a2a_version.go create mode 100644 go/core/internal/utils/a2a_version.go diff --git a/go/core/internal/a2a/a2a_handler_mux.go b/go/core/internal/a2a/a2a_handler_mux.go index af35cfb180..00d1eba356 100644 --- a/go/core/internal/a2a/a2a_handler_mux.go +++ b/go/core/internal/a2a/a2a_handler_mux.go @@ -8,6 +8,7 @@ import ( a2atype "github.com/a2aproject/a2a-go/v2/a2a" a2aclient "github.com/a2aproject/a2a-go/v2/a2aclient" + "github.com/a2aproject/a2a-go/v2/a2acompat/a2av0" "github.com/a2aproject/a2a-go/v2/a2asrv" "github.com/gorilla/mux" authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" @@ -59,7 +60,8 @@ func (a *handlerMux) SetAgentHandler( tracing middleware, ) error { requestHandler := a2asrv.NewHandler(NewPassthroughExecutor(client)) - jsonrpcHandler := a2asrv.NewJSONRPCHandler(requestHandler) + legacyJSONRPCHandler := a2av0.NewJSONRPCHandler(requestHandler) + v1JSONRPCHandler := a2asrv.NewJSONRPCHandler(requestHandler) cardHandler := a2asrv.NewStaticAgentCardHandler(&card) wellKnownPath := "/" + strings.TrimPrefix(a2asrv.WellKnownAgentCardPath, "/") @@ -68,7 +70,19 @@ func (a *handlerMux) SetAgentHandler( cardHandler.ServeHTTP(w, r) return } - jsonrpcHandler.ServeHTTP(w, r) + wireVersion, err := common.NegotiateA2AWireVersion(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + switch wireVersion { + case common.A2AWireVersionLegacy: + legacyJSONRPCHandler.ServeHTTP(w, r) + case common.A2AWireVersionV1: + v1JSONRPCHandler.ServeHTTP(w, r) + default: + http.Error(w, fmt.Sprintf("unknown negotiated A2A wire version %q", wireVersion), http.StatusBadRequest) + } }) middlewares := []middleware{authimpl.NewA2AAuthenticator(a.authenticator)} if tracing != nil { diff --git a/go/core/internal/a2a/a2a_registrar.go b/go/core/internal/a2a/a2a_registrar.go index 3a696f5d74..7defe3d95f 100644 --- a/go/core/internal/a2a/a2a_registrar.go +++ b/go/core/internal/a2a/a2a_registrar.go @@ -10,6 +10,7 @@ import ( a2atype "github.com/a2aproject/a2a-go/v2/a2a" a2aclient "github.com/a2aproject/a2a-go/v2/a2aclient" + "github.com/a2aproject/a2a-go/v2/a2acompat/a2av0" "github.com/go-logr/logr" "github.com/kagent-dev/kagent/go/api/v1alpha2" agent_translator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent" @@ -161,6 +162,17 @@ func (a *A2ARegistrar) upsertAgentHandler(ctx context.Context, agent v1alpha2.Ag // TODO: Switch this to 1.0 in release 0.11.0 when all agents are migrated to v1 filterInterfacesByVersion(card.SupportedInterfaces, a2atype.ProtocolVersion("0.3")), a2aclient.WithJSONRPCTransport(httpClient), + // TODO: Remove this in release 0.11.0 when all agents are migrated to v1 + a2aclient.WithCompatTransport( + a2atype.ProtocolVersion("0.3"), + a2atype.TransportProtocolJSONRPC, + a2aclient.TransportFactoryFn(func(_ context.Context, _ *a2atype.AgentCard, iface *a2atype.AgentInterface) (a2aclient.Transport, error) { + return a2av0.NewJSONRPCTransport(a2av0.JSONRPCTransportConfig{ + URL: iface.URL, + Client: httpClient, + }), nil + }), + ), a2aclient.WithCallInterceptors( NewUpstreamAuthInterceptor(a.authenticator, agentRef), ), diff --git a/go/core/internal/httpserver/handlers/a2a_version.go b/go/core/internal/httpserver/handlers/a2a_version.go deleted file mode 100644 index fcb715d52f..0000000000 --- a/go/core/internal/httpserver/handlers/a2a_version.go +++ /dev/null @@ -1,29 +0,0 @@ -package handlers - -import ( - "fmt" - "net/http" - - a2a "github.com/a2aproject/a2a-go/v2/a2a" -) - -type a2aWireVersion string - -const ( - a2aWireV0 a2aWireVersion = "v0" - a2aWireV1 a2aWireVersion = "v1" -) - -// negotiatedA2AWireVersion returns the A2A wire version negotiated by the client. -// Uses the constants exposed by a2a-go so this function is reusable for all versions in the future -func negotiatedA2AWireVersion(r *http.Request) (a2aWireVersion, error) { - version := r.Header.Get(a2a.SvcParamVersion) - switch version { - case "": - return a2aWireV0, nil - case string(a2a.Version): - return a2aWireV1, nil - default: - return "", fmt.Errorf("unsupported A2A version %q", version) - } -} diff --git a/go/core/internal/httpserver/handlers/sessions.go b/go/core/internal/httpserver/handlers/sessions.go index 05cd761ddc..0e985ed3ca 100644 --- a/go/core/internal/httpserver/handlers/sessions.go +++ b/go/core/internal/httpserver/handlers/sessions.go @@ -347,7 +347,7 @@ func (h *SessionsHandler) HandleListTasksForSession(w ErrorResponseWriter, r *ht w.RespondWithError(errors.NewInternalServerError("Failed to get session runs", err)) return } - wireVersion, err := negotiatedA2AWireVersion(r) + wireVersion, err := utils.NegotiateA2AWireVersion(r) if err != nil { w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", err)) return @@ -355,7 +355,7 @@ func (h *SessionsHandler) HandleListTasksForSession(w ErrorResponseWriter, r *ht log.Info("Successfully retrieved session tasks", "count", len(tasks)) switch wireVersion { - case a2aWireV0: + case utils.A2AWireVersionLegacy: legacyTasks := make([]any, 0, len(tasks)) for i := range tasks { legacyTask, convErr := trpcv0.ToLegacyTask(tasks[i]) @@ -367,7 +367,7 @@ func (h *SessionsHandler) HandleListTasksForSession(w ErrorResponseWriter, r *ht } data := api.NewResponse(legacyTasks, "Successfully retrieved session tasks", false) RespondWithJSON(w, http.StatusOK, data) - case a2aWireV1: + case utils.A2AWireVersionV1: data := api.NewResponse(tasks, "Successfully retrieved session tasks", false) RespondWithJSON(w, http.StatusOK, data) default: diff --git a/go/core/internal/httpserver/handlers/tasks.go b/go/core/internal/httpserver/handlers/tasks.go index 46a08f7a73..28b9d67426 100644 --- a/go/core/internal/httpserver/handlers/tasks.go +++ b/go/core/internal/httpserver/handlers/tasks.go @@ -7,6 +7,7 @@ import ( a2a "github.com/a2aproject/a2a-go/v2/a2a" api "github.com/kagent-dev/kagent/go/api/httpapi" "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" + "github.com/kagent-dev/kagent/go/core/internal/utils" "github.com/kagent-dev/kagent/go/core/pkg/a2acompat/trpcv0" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" "trpc.group/trpc-go/trpc-a2a-go/protocol" @@ -37,7 +38,7 @@ func (h *TasksHandler) HandleGetTask(w ErrorResponseWriter, r *http.Request) { w.RespondWithError(errors.NewNotFoundError("Task not found", err)) return } - wireVersion, err := negotiatedA2AWireVersion(r) + wireVersion, err := utils.NegotiateA2AWireVersion(r) if err != nil { w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", err)) return @@ -46,14 +47,14 @@ func (h *TasksHandler) HandleGetTask(w ErrorResponseWriter, r *http.Request) { log.Info("Successfully retrieved task") var data any switch wireVersion { - case a2aWireV0: + case utils.A2AWireVersionLegacy: legacyTask, convErr := trpcv0.ToLegacyTask(task) if convErr != nil { w.RespondWithError(errors.NewInternalServerError("Failed to convert task", convErr)) return } data = legacyTask - case a2aWireV1: + case utils.A2AWireVersionV1: data = task default: w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", fmt.Errorf("unknown negotiated wire version %q", wireVersion))) @@ -66,7 +67,7 @@ func (h *TasksHandler) HandleGetTask(w ErrorResponseWriter, r *http.Request) { func (h *TasksHandler) HandleCreateTask(w ErrorResponseWriter, r *http.Request) { log := ctrllog.FromContext(r.Context()).WithName("tasks-handler").WithValues("operation", "create-task") - wireVersion, err := negotiatedA2AWireVersion(r) + wireVersion, err := utils.NegotiateA2AWireVersion(r) if err != nil { w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", err)) return @@ -74,7 +75,7 @@ func (h *TasksHandler) HandleCreateTask(w ErrorResponseWriter, r *http.Request) task := a2a.Task{} switch wireVersion { - case a2aWireV0: + case utils.A2AWireVersionLegacy: legacyTask := protocol.Task{} if err := DecodeJSONBody(r, &legacyTask); err != nil { w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) @@ -88,7 +89,7 @@ func (h *TasksHandler) HandleCreateTask(w ErrorResponseWriter, r *http.Request) if converted != nil { task = *converted } - case a2aWireV1: + case utils.A2AWireVersionV1: if err := DecodeJSONBody(r, &task); err != nil { w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) return @@ -110,14 +111,14 @@ func (h *TasksHandler) HandleCreateTask(w ErrorResponseWriter, r *http.Request) log.Info("Successfully created task") var data any switch wireVersion { - case a2aWireV0: + case utils.A2AWireVersionLegacy: legacyTask, convErr := trpcv0.ToLegacyTask(&task) if convErr != nil { w.RespondWithError(errors.NewInternalServerError("Failed to convert task", convErr)) return } data = legacyTask - case a2aWireV1: + case utils.A2AWireVersionV1: data = task default: w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", fmt.Errorf("unknown negotiated wire version %q", wireVersion))) diff --git a/go/core/internal/utils/a2a_version.go b/go/core/internal/utils/a2a_version.go new file mode 100644 index 0000000000..c6eaf05d2e --- /dev/null +++ b/go/core/internal/utils/a2a_version.go @@ -0,0 +1,30 @@ +package utils + +import ( + "fmt" + "net/http" + + a2atype "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/a2aproject/a2a-go/v2/a2acompat/a2av0" +) + +type A2AWireVersion string + +const ( + A2AWireVersionLegacy A2AWireVersion = "v0" + A2AWireVersionV1 A2AWireVersion = "v1" +) + +// NegotiateA2AWireVersion returns the A2A wire version requested by the client. +// Missing or explicit 0.3 headers use the legacy/current kagent A2A wire shape. +func NegotiateA2AWireVersion(r *http.Request) (A2AWireVersion, error) { + version := r.Header.Get(a2atype.SvcParamVersion) + switch version { + case "", string(a2av0.Version): + return A2AWireVersionLegacy, nil + case string(a2atype.Version): + return A2AWireVersionV1, nil + default: + return "", fmt.Errorf("unsupported A2A version %q", version) + } +} From a71c2565ec0f98848ec75a7282b1e0b89ec8b154 Mon Sep 17 00:00:00 2001 From: Jet Chiang Date: Mon, 25 May 2026 17:35:35 -0400 Subject: [PATCH 3/8] some more fixes w agentcard and routes Signed-off-by: Jet Chiang --- go/core/internal/a2a/a2a_handler_mux.go | 5 +- go/core/internal/a2a/passthrough_handler.go | 82 +++++++++++++++++++ .../translator/agent/manifest_builder.go | 15 +++- .../outputs/agent_with_a2a_config.json | 4 +- .../outputs/agent_with_allowed_headers.json | 4 +- .../testdata/outputs/agent_with_code.json | 4 +- .../outputs/agent_with_context_config.json | 4 +- .../agent_with_cross_namespace_tools.json | 4 +- .../outputs/agent_with_custom_sa.json | 4 +- .../outputs/agent_with_default_sa.json | 4 +- .../agent_with_embedding_provider.json | 4 +- .../outputs/agent_with_extra_containers.json | 4 +- .../outputs/agent_with_git_skills.json | 4 +- .../outputs/agent_with_http_toolserver.json | 4 +- .../outputs/agent_with_mcp_service.json | 4 +- .../testdata/outputs/agent_with_memory.json | 4 +- .../outputs/agent_with_nested_agent.json | 4 +- .../outputs/agent_with_passthrough.json | 4 +- .../outputs/agent_with_prompt_template.json | 4 +- .../testdata/outputs/agent_with_proxy.json | 4 +- .../agent_with_proxy_external_remotemcp.json | 4 +- .../outputs/agent_with_proxy_mcpserver.json | 4 +- ...t_with_proxy_mcpserver_custom_timeout.json | 4 +- .../outputs/agent_with_proxy_service.json | 4 +- .../outputs/agent_with_require_approval.json | 4 +- .../agent_with_scheduling_attributes.json | 4 +- .../outputs/agent_with_security_context.json | 4 +- .../testdata/outputs/agent_with_skills.json | 4 +- .../outputs/agent_with_streaming.json | 4 +- ...nt_with_system_message_from_configmap.json | 4 +- ...agent_with_system_message_from_secret.json | 4 +- .../testdata/outputs/anthropic_agent.json | 4 +- .../agent/testdata/outputs/basic_agent.json | 4 +- .../agent/testdata/outputs/bedrock_agent.json | 4 +- .../agent/testdata/outputs/ollama_agent.json | 4 +- .../testdata/outputs/tls-with-custom-ca.json | 4 +- .../outputs/tls-with-disabled-verify.json | 4 +- .../outputs/tls-with-system-cas-disabled.json | 4 +- ui/src/app/actions/servers.ts | 2 +- 39 files changed, 168 insertions(+), 76 deletions(-) create mode 100644 go/core/internal/a2a/passthrough_handler.go diff --git a/go/core/internal/a2a/a2a_handler_mux.go b/go/core/internal/a2a/a2a_handler_mux.go index 00d1eba356..3cbbadf50f 100644 --- a/go/core/internal/a2a/a2a_handler_mux.go +++ b/go/core/internal/a2a/a2a_handler_mux.go @@ -59,10 +59,11 @@ func (a *handlerMux) SetAgentHandler( card a2atype.AgentCard, tracing middleware, ) error { - requestHandler := a2asrv.NewHandler(NewPassthroughExecutor(client)) + // TODO: Remove this in release 0.11.0 when all agents are migrated to v1 + requestHandler := NewPassthroughRequestHandler(client, &card) legacyJSONRPCHandler := a2av0.NewJSONRPCHandler(requestHandler) v1JSONRPCHandler := a2asrv.NewJSONRPCHandler(requestHandler) - cardHandler := a2asrv.NewStaticAgentCardHandler(&card) + cardHandler := a2asrv.NewAgentCardHandler(a2av0.NewStaticAgentCardProducer(&card)) wellKnownPath := "/" + strings.TrimPrefix(a2asrv.WellKnownAgentCardPath, "/") var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/go/core/internal/a2a/passthrough_handler.go b/go/core/internal/a2a/passthrough_handler.go new file mode 100644 index 0000000000..60a99263ef --- /dev/null +++ b/go/core/internal/a2a/passthrough_handler.go @@ -0,0 +1,82 @@ +package a2a + +import ( + "context" + "iter" + + a2atype "github.com/a2aproject/a2a-go/v2/a2a" + a2aclient "github.com/a2aproject/a2a-go/v2/a2aclient" + "github.com/a2aproject/a2a-go/v2/a2asrv" +) + +type PassthroughRequestHandler struct { + client *a2aclient.Client + card *a2atype.AgentCard +} + +var _ a2asrv.RequestHandler = (*PassthroughRequestHandler)(nil) + +// NewPassthroughRequestHandler returns a transport-level proxy for controller +// A2A endpoints. It delegates each request directly to the selected upstream +// agent client and intentionally bypasses a2asrv.NewHandler, which would create +// local task state and apply v1 task-processing invariants to legacy streams. +// Keep this while the controller bridges mixed 0.3 and 1.0 clients/runtimes; +// once all supported traffic is native v1, the controller can use the standard +// v1 handler stack directly. +func NewPassthroughRequestHandler(client *a2aclient.Client, card *a2atype.AgentCard) *PassthroughRequestHandler { + return &PassthroughRequestHandler{ + client: client, + card: card, + } +} + +func (h *PassthroughRequestHandler) GetTask(ctx context.Context, req *a2atype.GetTaskRequest) (*a2atype.Task, error) { + return h.client.GetTask(ctx, req) +} + +func (h *PassthroughRequestHandler) ListTasks(ctx context.Context, req *a2atype.ListTasksRequest) (*a2atype.ListTasksResponse, error) { + return h.client.ListTasks(ctx, req) +} + +func (h *PassthroughRequestHandler) CancelTask(ctx context.Context, req *a2atype.CancelTaskRequest) (*a2atype.Task, error) { + return h.client.CancelTask(ctx, req) +} + +func (h *PassthroughRequestHandler) SendMessage(ctx context.Context, req *a2atype.SendMessageRequest) (a2atype.SendMessageResult, error) { + return h.client.SendMessage(ctx, req) +} + +func (h *PassthroughRequestHandler) SubscribeToTask(ctx context.Context, req *a2atype.SubscribeToTaskRequest) iter.Seq2[a2atype.Event, error] { + return h.client.SubscribeToTask(ctx, req) +} + +func (h *PassthroughRequestHandler) SendStreamingMessage(ctx context.Context, req *a2atype.SendMessageRequest) iter.Seq2[a2atype.Event, error] { + return h.client.SendStreamingMessage(ctx, req) +} + +func (h *PassthroughRequestHandler) GetTaskPushConfig(ctx context.Context, req *a2atype.GetTaskPushConfigRequest) (*a2atype.PushConfig, error) { + return h.client.GetTaskPushConfig(ctx, req) +} + +func (h *PassthroughRequestHandler) ListTaskPushConfigs(ctx context.Context, req *a2atype.ListTaskPushConfigRequest) (*a2atype.ListTaskPushConfigResponse, error) { + configs, err := h.client.ListTaskPushConfigs(ctx, req) + if err != nil { + return nil, err + } + return &a2atype.ListTaskPushConfigResponse{Configs: configs}, nil +} + +func (h *PassthroughRequestHandler) CreateTaskPushConfig(ctx context.Context, req *a2atype.PushConfig) (*a2atype.PushConfig, error) { + return h.client.CreateTaskPushConfig(ctx, req) +} + +func (h *PassthroughRequestHandler) DeleteTaskPushConfig(ctx context.Context, req *a2atype.DeleteTaskPushConfigRequest) error { + return h.client.DeleteTaskPushConfig(ctx, req) +} + +func (h *PassthroughRequestHandler) GetExtendedAgentCard(ctx context.Context, req *a2atype.GetExtendedAgentCardRequest) (*a2atype.AgentCard, error) { + if h.card != nil && !h.card.Capabilities.ExtendedAgentCard { + return h.card, nil + } + return h.client.GetExtendedAgentCard(ctx, req) +} diff --git a/go/core/internal/controller/translator/agent/manifest_builder.go b/go/core/internal/controller/translator/agent/manifest_builder.go index 005c397737..befb8e43da 100644 --- a/go/core/internal/controller/translator/agent/manifest_builder.go +++ b/go/core/internal/controller/translator/agent/manifest_builder.go @@ -7,6 +7,8 @@ import ( "maps" a2a "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/a2aproject/a2a-go/v2/a2acompat/a2av0" + "github.com/a2aproject/a2a-go/v2/a2asrv" "github.com/kagent-dev/kagent/go/api/adk" "github.com/kagent-dev/kagent/go/api/v1alpha2" "github.com/kagent-dev/kagent/go/core/internal/controller/translator/labels" @@ -57,7 +59,7 @@ func (a *adkApiTranslator) BuildManifest( outputs := &AgentOutputs{} manifestCtx := newManifestContext(agent, inputs.Deployment) - configSecret, err := a.buildConfigSecret(manifestCtx, inputs.Config, inputs.Sandbox, inputs.AgentCard, inputs.SecretHashBytes) + configSecret, err := a.buildConfigSecret(ctx, manifestCtx, inputs.Config, inputs.Sandbox, inputs.AgentCard, inputs.SecretHashBytes) if err != nil { return nil, err } @@ -125,6 +127,7 @@ func (m manifestContext) objectMeta() metav1.ObjectMeta { } func (a *adkApiTranslator) buildConfigSecret( + ctx context.Context, manifestCtx manifestContext, cfg *adk.AgentConfig, sandboxCfg *v1alpha2.SandboxConfig, @@ -146,11 +149,17 @@ func (a *adkApiTranslator) buildConfigSecret( cfgJSON = string(bCfg) } if card != nil { - bCard, err := json.Marshal(card) + // TODO: replace this with the v1 agent card producer in release 0.11.0 + producer := a2av0.NewStaticAgentCardProducer(card) + jsonProducer, ok := producer.(a2asrv.AgentCardJSONProducer) + if !ok { + return nil, fmt.Errorf("compat agent card producer does not support JSON serialization") + } + cardJSON, err := jsonProducer.CardJSON(ctx) if err != nil { return nil, err } - agentCard = string(bCard) + agentCard = string(cardJSON) } if needsSRTSettings(manifestCtx.agent, sandboxCfg) { bSRTSettings, err := buildSRTSettingsJSON(sandboxCfg) diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_a2a_config.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_a2a_config.json index bc4a1117f7..e2cb0e84b8 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_a2a_config.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_a2a_config.json @@ -68,7 +68,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://a2a-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://a2a-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"a2a_agent\",\"skills\":[{\"description\":\"Summarizes text\",\"id\":\"summarize\",\"name\":\"Summarize\",\"tags\":null}],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"a2a_agent\",\n \"version\": \"\",\n \"skills\": [\n {\n \"description\": \"Summarizes text\",\n \"id\": \"summarize\",\n \"name\": \"Summarize\",\n \"tags\": null\n }\n ],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://a2a-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://a2a-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://a2a-agent.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}" } }, @@ -138,7 +138,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "9207770362976028198" + "kagent.dev/config-hash": "11777198347800362314" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_allowed_headers.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_allowed_headers.json index 5d300ce603..acd88ecdc5 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_allowed_headers.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_allowed_headers.json @@ -78,7 +78,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"http_tools\":[{\"params\":{\"url\":\"http://mcp-server.test:8080/mcp\",\"headers\":{}},\"tools\":[\"tool1\",\"tool2\"],\"allowed_headers\":[\"x-user-email\",\"x-tenant-id\"]}],\"stream\":false}" } }, @@ -148,7 +148,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "2138527890147516382" + "kagent.dev/config-hash": "7055658815424891365" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_code.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_code.json index 57690d3946..c959a47d6f 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_code.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_code.json @@ -70,7 +70,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-code.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-code.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_code\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent_with_code\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-with-code.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-with-code.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-with-code.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"reasoning_effort\":\"low\",\"temperature\":0.7,\"top_p\":0.95},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"execute_code\":true,\"stream\":false}", "srt-settings.json": "{\"filesystem\":{\"allowWrite\":[\".\",\"/tmp\"],\"denyRead\":[],\"denyWrite\":[]},\"network\":{\"allowedDomains\":[],\"deniedDomains\":[]}}" } @@ -141,7 +141,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "15600117866655840721" + "kagent.dev/config-hash": "13638639139652067632" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_context_config.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_context_config.json index d2a8cc6780..16b91cefdf 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_context_config.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_context_config.json @@ -80,7 +80,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-context.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-context.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"Agent with context management\",\"name\":\"agent_with_context\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"Agent with context management\",\n \"name\": \"agent_with_context\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-with-context.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-with-context.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-with-context.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"temperature\":0.7},\"description\":\"Agent with context management\",\"instruction\":\"You are a helpful assistant with context management enabled.\",\"stream\":false,\"context_config\":{\"compaction\":{\"compaction_interval\":5,\"overlap_size\":2,\"summarizer_model\":{\"type\":\"anthropic\",\"model\":\"claude-3-haiku\"},\"prompt_template\":\"Summarize the following conversation events concisely.\",\"token_threshold\":50000,\"event_retention_size\":10}}}" } }, @@ -150,7 +150,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "16190589400428186426" + "kagent.dev/config-hash": "7947347043949539668" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_cross_namespace_tools.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_cross_namespace_tools.json index 96cba3c405..283963cd96 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_cross_namespace_tools.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_cross_namespace_tools.json @@ -84,7 +84,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://source-agent.source-ns:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://source-agent.source-ns:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"An agent that uses cross-namespace tools\",\"name\":\"source_agent\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"An agent that uses cross-namespace tools\",\n \"name\": \"source_agent\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://source-agent.source-ns:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://source-agent.source-ns:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://source-agent.source-ns:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"An agent that uses cross-namespace tools\",\"instruction\":\"You are an assistant with access to shared tools.\",\"http_tools\":[{\"params\":{\"url\":\"http://tools.tools-ns.svc:8080/mcp\",\"headers\":{\"Authorization\":\"tool-secret-token\"},\"timeout\":30},\"tools\":[\"list_resources\",\"get_resource\"]}],\"remote_agents\":[{\"name\":\"tools_ns__NS__tools_agent\",\"url\":\"http://tools-agent.tools-ns:8080\",\"description\":\"An agent that can be used as a cross-namespace tool\"}],\"stream\":false}" } }, @@ -154,7 +154,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "9411017991045658659" + "kagent.dev/config-hash": "6928733945253389376" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_custom_sa.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_custom_sa.json index 5bd3760274..26de14963f 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_custom_sa.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_custom_sa.json @@ -62,7 +62,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-custom-sa.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-custom-sa.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_custom_sa\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent_with_custom_sa\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-with-custom-sa.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-with-custom-sa.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-with-custom-sa.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}" } }, @@ -107,7 +107,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "11401466948750956923" + "kagent.dev/config-hash": "4875306436828307681" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_default_sa.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_default_sa.json index 595bfbf6ec..1f8ba2ae2e 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_default_sa.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_default_sa.json @@ -62,7 +62,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-default-sa.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-default-sa.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_default_sa\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent_with_default_sa\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-with-default-sa.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-with-default-sa.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-with-default-sa.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}" } }, @@ -107,7 +107,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "1706358953235401870" + "kagent.dev/config-hash": "1952368464262477618" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_embedding_provider.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_embedding_provider.json index 243e517582..d018d872cf 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_embedding_provider.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_embedding_provider.json @@ -68,7 +68,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-cross-provider-memory.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-cross-provider-memory.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_cross_provider_memory\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent_cross_provider_memory\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-cross-provider-memory.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-cross-provider-memory.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-cross-provider-memory.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are an assistant.\",\"stream\":false,\"memory\":{\"embedding\":{\"provider\":\"gemini_vertex_ai\",\"model\":\"text-embedding-005\"}}}" } }, @@ -138,7 +138,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "12134627225846032635" + "kagent.dev/config-hash": "6685832133934502607" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_extra_containers.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_extra_containers.json index b2e78f8c4f..67a8e8a2f4 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_extra_containers.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_extra_containers.json @@ -62,7 +62,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-extra-containers.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-extra-containers.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_extra_containers\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent_with_extra_containers\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-with-extra-containers.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-with-extra-containers.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-with-extra-containers.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}" } }, @@ -132,7 +132,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "5464807304477718486" + "kagent.dev/config-hash": "5942407443192129450" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_git_skills.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_git_skills.json index f1f27f8053..aa4b77207a 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_git_skills.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_git_skills.json @@ -69,7 +69,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://git-skills-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://git-skills-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"git_skills_agent\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"git_skills_agent\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://git-skills-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://git-skills-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://git-skills-agent.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"reasoning_effort\":\"low\",\"temperature\":0.7,\"top_p\":0.95},\"description\":\"\",\"instruction\":\"You are a helpful assistant with skills from git.\",\"stream\":false}", "srt-settings.json": "{\"filesystem\":{\"allowWrite\":[\".\",\"/tmp\"],\"denyRead\":[],\"denyWrite\":[]},\"network\":{\"allowedDomains\":[],\"deniedDomains\":[]}}" } @@ -140,7 +140,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "18122633214966413236" + "kagent.dev/config-hash": "10177226668840890883" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_http_toolserver.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_http_toolserver.json index 4e3e475fae..bd5f97ca4a 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_http_toolserver.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_http_toolserver.json @@ -77,7 +77,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a math toolserver. Focus on solving mathematical problems step by step.\",\"http_tools\":[{\"params\":{\"url\":\"http://localhost:8084/mcp\",\"headers\":{\"MATH\":\"sk-test-api-key\"},\"timeout\":30,\"sse_read_timeout\":300},\"tools\":[\"k8s_get_resources\"]}],\"stream\":false}" } }, @@ -147,7 +147,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "8395429179909149115" + "kagent.dev/config-hash": "8643793714945604953" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_mcp_service.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_mcp_service.json index 56c521d3f1..440b385bac 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_mcp_service.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_mcp_service.json @@ -73,7 +73,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a math toolserver. Focus on solving mathematical problems step by step.\",\"http_tools\":[{\"params\":{\"url\":\"http://toolserver.test:8084/mcp\",\"headers\":{}},\"tools\":[\"k8s_get_resources\"]}],\"stream\":false}" } }, @@ -143,7 +143,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "17883346513180388434" + "kagent.dev/config-hash": "90499429102488578" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_memory.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_memory.json index 97d6a583e3..940037c668 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_memory.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_memory.json @@ -73,7 +73,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-memory.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-memory.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_memory\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent_with_memory\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-with-memory.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-with-memory.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-with-memory.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"temperature\":0.7},\"description\":\"\",\"instruction\":\"You are a helpful assistant with memory. Save important findings and use past context when relevant.\",\"stream\":false,\"memory\":{\"embedding\":{\"provider\":\"openai\",\"model\":\"text-embedding-3-small\"}}}" } }, @@ -143,7 +143,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "2292760194225518181" + "kagent.dev/config-hash": "4675326514170066600" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_nested_agent.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_nested_agent.json index 9f8484bdb7..4183fe912c 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_nested_agent.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_nested_agent.json @@ -71,7 +71,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://parent-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://parent-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"parent_agent\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"parent_agent\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://parent-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://parent-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://parent-agent.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are a coordinating agent that can delegate tasks to specialists.\",\"remote_agents\":[{\"name\":\"test__NS__specialist_agent\",\"url\":\"http://specialist-agent.test:8080\",\"headers\":{\"FOO\":\"sup3rs3cr3t\"}}],\"stream\":false}" } }, @@ -141,7 +141,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "5732257866142725806" + "kagent.dev/config-hash": "12309204394800027371" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_passthrough.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_passthrough.json index 48ca6f511f..2cf6122979 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_passthrough.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_passthrough.json @@ -64,7 +64,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://passthrough-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://passthrough-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"passthrough_agent\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"passthrough_agent\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://passthrough-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://passthrough-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://passthrough-agent.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"api_key_passthrough\":true,\"base_url\":\"\",\"temperature\":0.7},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}" } }, @@ -134,7 +134,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "15783211955608997214" + "kagent.dev/config-hash": "15376076214432010811" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_prompt_template.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_prompt_template.json index 4ce449cef0..8a7f398b70 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_prompt_template.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_prompt_template.json @@ -75,7 +75,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-prompt-template.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-prompt-template.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"A Kubernetes troubleshooting agent\",\"name\":\"agent_with_prompt_template\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"A Kubernetes troubleshooting agent\",\n \"name\": \"agent_with_prompt_template\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-with-prompt-template.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-with-prompt-template.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-with-prompt-template.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"A Kubernetes troubleshooting agent\",\"instruction\":\"## Preamble\\nYou are a helpful Kubernetes assistant.\\n\\n\\nYou are agent-with-prompt-template, operating in test.\\nYour purpose: A Kubernetes troubleshooting agent\\n\\nAvailable tools: k8s_get_resources, k8s_describe_resource, \\n\\n## Safety Guidelines\\nNever delete resources without explicit user confirmation.\\n\\n\",\"http_tools\":[{\"params\":{\"url\":\"http://localhost:8084/mcp\",\"headers\":{},\"timeout\":30},\"tools\":[\"k8s_get_resources\",\"k8s_describe_resource\"]}],\"stream\":false}" } }, @@ -145,7 +145,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "1641050374260693601" + "kagent.dev/config-hash": "2608374849770441844" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy.json index 27db580483..666a1fb10b 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy.json @@ -84,7 +84,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-proxy.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-proxy.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_proxy\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent_with_proxy\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-with-proxy.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-with-proxy.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-with-proxy.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are an agent that uses proxies.\",\"http_tools\":[{\"params\":{\"url\":\"http://proxy.kagent.svc.cluster.local:8080/mcp\",\"headers\":{\"x-kagent-host\":\"test-mcp-server.kagent\"}},\"tools\":[\"test-tool\"]}],\"remote_agents\":[{\"name\":\"test__NS__nested_agent\",\"url\":\"http://proxy.kagent.svc.cluster.local:8080\",\"headers\":{\"x-kagent-host\":\"nested-agent.test\"}}],\"stream\":false}" } }, @@ -154,7 +154,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "9229675023087394140" + "kagent.dev/config-hash": "6086147857331851037" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_external_remotemcp.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_external_remotemcp.json index cecccab26a..1dab97f4c6 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_external_remotemcp.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_external_remotemcp.json @@ -73,7 +73,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-proxy-external.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-proxy-external.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_proxy_external\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent_with_proxy_external\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-with-proxy-external.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-with-proxy-external.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-with-proxy-external.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are an agent that uses proxies.\",\"http_tools\":[{\"params\":{\"url\":\"https://external-mcp.example.com/mcp\",\"headers\":{}},\"tools\":[\"test-tool\"]}],\"stream\":false}" } }, @@ -143,7 +143,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "15837079965376691471" + "kagent.dev/config-hash": "16116210188225830835" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver.json index c15712741f..ce1409ab7c 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver.json @@ -76,7 +76,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-proxy-mcpserver.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-proxy-mcpserver.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_proxy_mcpserver\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent_with_proxy_mcpserver\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-with-proxy-mcpserver.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-with-proxy-mcpserver.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-with-proxy-mcpserver.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are an agent that uses proxies.\",\"http_tools\":[{\"params\":{\"url\":\"http://proxy.kagent.svc.cluster.local:8080/mcp\",\"headers\":{\"x-kagent-host\":\"test-mcp-server.test\"},\"timeout\":30},\"tools\":[\"test-tool\"]}],\"stream\":false}" } }, @@ -146,7 +146,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "12725512234622067294" + "kagent.dev/config-hash": "699819535100768323" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver_custom_timeout.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver_custom_timeout.json index cb42efabec..66199196a8 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver_custom_timeout.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver_custom_timeout.json @@ -76,7 +76,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-proxy-mcpserver-timeout.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-proxy-mcpserver-timeout.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_proxy_mcpserver_timeout\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent_with_proxy_mcpserver_timeout\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-with-proxy-mcpserver-timeout.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-with-proxy-mcpserver-timeout.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-with-proxy-mcpserver-timeout.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are an agent that uses proxies.\",\"http_tools\":[{\"params\":{\"url\":\"http://proxy.kagent.svc.cluster.local:8080/mcp\",\"headers\":{\"x-kagent-host\":\"test-mcp-server.test\"},\"timeout\":60},\"tools\":[\"test-tool\"]}],\"stream\":false}" } }, @@ -146,7 +146,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "8496090108219408384" + "kagent.dev/config-hash": "5660639111005887260" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_service.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_service.json index ad72b12953..4a03d166d1 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_service.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_service.json @@ -75,7 +75,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-proxy-service.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-proxy-service.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_proxy_service\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent_with_proxy_service\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-with-proxy-service.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-with-proxy-service.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-with-proxy-service.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You are an agent that uses proxies.\",\"http_tools\":[{\"params\":{\"url\":\"http://proxy.kagent.svc.cluster.local:8080/mcp\",\"headers\":{\"x-kagent-host\":\"toolserver.test\"}},\"tools\":[\"k8s_get_resources\"]}],\"stream\":false}" } }, @@ -145,7 +145,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "4422521160223291204" + "kagent.dev/config-hash": "2741762687963053248" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_require_approval.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_require_approval.json index 09162b02d5..e592026a1e 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_require_approval.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_require_approval.json @@ -79,7 +79,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You help users manage files.\",\"http_tools\":[{\"params\":{\"url\":\"http://toolserver.test:8084/mcp\",\"headers\":{}},\"tools\":[\"read_file\",\"write_file\",\"delete_file\"],\"require_approval\":[\"delete_file\",\"write_file\"]}],\"stream\":false}" } }, @@ -149,7 +149,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "9815976453219091618" + "kagent.dev/config-hash": "8217777934714265853" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_scheduling_attributes.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_scheduling_attributes.json index 87b51ff4b7..fd9524ce33 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_scheduling_attributes.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_scheduling_attributes.json @@ -69,7 +69,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-scheduling-attributes.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-scheduling-attributes.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_scheduling_attributes\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent_with_scheduling_attributes\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-with-scheduling-attributes.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-with-scheduling-attributes.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-with-scheduling-attributes.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"reasoning_effort\":\"low\",\"temperature\":0.7,\"top_p\":0.95},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}" } }, @@ -139,7 +139,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "14381130188719579868" + "kagent.dev/config-hash": "1301028021327278116" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_security_context.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_security_context.json index 2785e6af5b..028a009568 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_security_context.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_security_context.json @@ -69,7 +69,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-security-context.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-security-context.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_security_context\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent_with_security_context\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-with-security-context.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-with-security-context.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-with-security-context.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"reasoning_effort\":\"low\",\"temperature\":0.7,\"top_p\":0.95},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}" } }, @@ -139,7 +139,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "9168209007673468962" + "kagent.dev/config-hash": "3654896060561973296" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_skills.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_skills.json index 60d60b1ef0..91f1fe968d 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_skills.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_skills.json @@ -69,7 +69,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://skills-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://skills-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"skills_agent\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"skills_agent\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://skills-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://skills-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://skills-agent.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"reasoning_effort\":\"low\",\"temperature\":0.7,\"top_p\":0.95},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}", "srt-settings.json": "{\"filesystem\":{\"allowWrite\":[\".\",\"/tmp\"],\"denyRead\":[],\"denyWrite\":[]},\"network\":{\"allowedDomains\":[],\"deniedDomains\":[]}}" } @@ -140,7 +140,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "3788882410372515592" + "kagent.dev/config-hash": "1709213079642646235" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_streaming.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_streaming.json index f49f50d0ad..ccc3ddc1c0 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_streaming.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_streaming.json @@ -69,7 +69,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://basic-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://basic-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"basic_agent\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"basic_agent\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://basic-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://basic-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://basic-agent.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"reasoning_effort\":\"low\",\"temperature\":0.7,\"top_p\":0.95},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":true}" } }, @@ -139,7 +139,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "739336962982548496" + "kagent.dev/config-hash": "7925219030441964607" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_configmap.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_configmap.json index f58a025694..a8ad8d0127 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_configmap.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_configmap.json @@ -62,7 +62,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-configmap-system-message.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-configmap-system-message.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_configmap_system_message\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent_with_configmap_system_message\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-with-configmap-system-message.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-with-configmap-system-message.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-with-configmap-system-message.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"Speak in the style of Shakespeare.\",\"stream\":false}" } }, @@ -132,7 +132,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "9080902236721123646" + "kagent.dev/config-hash": "14676988129172891564" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_secret.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_secret.json index 61ca9af37c..afb1453bec 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_secret.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_secret.json @@ -62,7 +62,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://agent-with-secret-system-message.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://agent-with-secret-system-message.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"agent_with_secret_system_message\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"agent_with_secret_system_message\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://agent-with-secret-system-message.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://agent-with-secret-system-message.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://agent-with-secret-system-message.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"base_url\":\"\"},\"description\":\"\",\"instruction\":\"You will speak in the style of Shakespeare.\\n\",\"stream\":false}" } }, @@ -132,7 +132,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "18027380751492011100" + "kagent.dev/config-hash": "13184379951838451524" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/anthropic_agent.json b/go/core/internal/controller/translator/agent/testdata/outputs/anthropic_agent.json index 106fa7ebed..abeecdbb99 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/anthropic_agent.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/anthropic_agent.json @@ -61,7 +61,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://anthropic-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://anthropic-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"anthropic_agent\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"anthropic_agent\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://anthropic-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://anthropic-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://anthropic-agent.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"anthropic\",\"model\":\"claude-3-sonnet-20240229\"},\"description\":\"\",\"instruction\":\"You are Claude, an AI assistant created by Anthropic.\",\"stream\":false}" } }, @@ -131,7 +131,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "8555039268477979079" + "kagent.dev/config-hash": "1266575541990203530" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/basic_agent.json b/go/core/internal/controller/translator/agent/testdata/outputs/basic_agent.json index 11def185e9..af7e049ef8 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/basic_agent.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/basic_agent.json @@ -69,7 +69,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://basic-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://basic-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"basic_agent\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"basic_agent\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://basic-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://basic-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://basic-agent.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"base_url\":\"\",\"max_tokens\":1024,\"reasoning_effort\":\"low\",\"temperature\":0.7,\"top_p\":0.95},\"description\":\"\",\"instruction\":\"You are a helpful assistant.\",\"stream\":false}" } }, @@ -139,7 +139,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "17570186208944924589" + "kagent.dev/config-hash": "13011412982718834568" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/bedrock_agent.json b/go/core/internal/controller/translator/agent/testdata/outputs/bedrock_agent.json index 9c8652b8f2..7f30e79f06 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/bedrock_agent.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/bedrock_agent.json @@ -62,7 +62,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://bedrock-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://bedrock-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"bedrock_agent\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"bedrock_agent\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://bedrock-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://bedrock-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://bedrock-agent.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"bedrock\",\"model\":\"us.anthropic.claude-sonnet-4-20250514-v1:0\",\"region\":\"us-east-1\"},\"description\":\"\",\"instruction\":\"You are a helpful AI assistant running on AWS Bedrock.\",\"stream\":false}" } }, @@ -132,7 +132,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "680468057177931757" + "kagent.dev/config-hash": "10296132164457756348" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/ollama_agent.json b/go/core/internal/controller/translator/agent/testdata/outputs/ollama_agent.json index 5943be5774..409e5d175d 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/ollama_agent.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/ollama_agent.json @@ -69,7 +69,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://ollama-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://ollama-agent.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"ollama_agent\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"ollama_agent\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://ollama-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://ollama-agent.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://ollama-agent.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"ollama\",\"model\":\"llama3.2:latest\",\"headers\":{\"User-Agent\":\"kagent/1.0\"},\"options\":{\"num_ctx\":\"2048\",\"temperature\":\"0.8\",\"top_p\":\"0.9\"}},\"description\":\"\",\"instruction\":\"You are a helpful AI assistant running locally via Ollama.\",\"stream\":false}" } }, @@ -139,7 +139,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "11557155459231859304" + "kagent.dev/config-hash": "16198809549325160576" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-custom-ca.json b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-custom-ca.json index e98c0fd680..2039cad5c1 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-custom-ca.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-custom-ca.json @@ -67,7 +67,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://tls-agent-with-custom-ca.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://tls-agent-with-custom-ca.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"tls_agent_with_custom_ca\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"tls_agent_with_custom_ca\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://tls-agent-with-custom-ca.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://tls-agent-with-custom-ca.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://tls-agent-with-custom-ca.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"tls_insecure_skip_verify\":false,\"tls_ca_cert_path\":\"/etc/ssl/certs/custom/ca.crt\",\"tls_disable_system_cas\":false,\"base_url\":\"https://internal-litellm.company.com\",\"max_tokens\":1024,\"temperature\":0.7},\"description\":\"\",\"instruction\":\"You are a helpful assistant with custom CA support.\",\"stream\":false}" } }, @@ -137,7 +137,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "15757998895553750808" + "kagent.dev/config-hash": "1274635499647008262" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-disabled-verify.json b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-disabled-verify.json index 932b421f64..1eebea8aff 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-disabled-verify.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-disabled-verify.json @@ -66,7 +66,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://tls-agent-with-disabled-verify.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://tls-agent-with-disabled-verify.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"tls_agent_with_disabled_verify\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"tls_agent_with_disabled_verify\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://tls-agent-with-disabled-verify.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://tls-agent-with-disabled-verify.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://tls-agent-with-disabled-verify.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"tls_insecure_skip_verify\":true,\"tls_disable_system_cas\":false,\"base_url\":\"https://dev-litellm.local\",\"max_tokens\":1024,\"temperature\":0.7},\"description\":\"\",\"instruction\":\"You are a helpful assistant in a development environment.\",\"stream\":false}" } }, @@ -136,7 +136,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "7733456931475125811" + "kagent.dev/config-hash": "12364563615889863360" }, "labels": { "app": "kagent", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-system-cas-disabled.json b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-system-cas-disabled.json index e5f88365f0..2dbb8c1eed 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-system-cas-disabled.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-system-cas-disabled.json @@ -67,7 +67,7 @@ ] }, "stringData": { - "agent-card.json": "{\"supportedInterfaces\":[{\"url\":\"http://tls-agent-with-system-cas-disabled.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"0.3\"},{\"url\":\"http://tls-agent-with-system-cas-disabled.test:8080\",\"protocolBinding\":\"JSONRPC\",\"protocolVersion\":\"1.0\"}],\"capabilities\":{\"streaming\":true},\"defaultInputModes\":[\"text\"],\"defaultOutputModes\":[\"text\"],\"description\":\"\",\"name\":\"tls_agent_with_system_cas_disabled\",\"skills\":[],\"version\":\"\"}", + "agent-card.json": "{\n \"defaultInputModes\": [\n \"text\"\n ],\n \"defaultOutputModes\": [\n \"text\"\n ],\n \"description\": \"\",\n \"name\": \"tls_agent_with_system_cas_disabled\",\n \"version\": \"\",\n \"skills\": [],\n \"capabilities\": {\n \"streaming\": true\n },\n \"supportedInterfaces\": [\n {\n \"url\": \"http://tls-agent-with-system-cas-disabled.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"0.3\"\n },\n {\n \"url\": \"http://tls-agent-with-system-cas-disabled.test:8080\",\n \"protocolBinding\": \"JSONRPC\",\n \"protocolVersion\": \"1.0\"\n }\n ],\n \"url\": \"http://tls-agent-with-system-cas-disabled.test:8080\",\n \"protocolVersion\": \"0.3\",\n \"preferredTransport\": \"JSONRPC\"\n}", "config.json": "{\"model\":{\"type\":\"openai\",\"model\":\"gpt-4o\",\"tls_insecure_skip_verify\":false,\"tls_ca_cert_path\":\"/etc/ssl/certs/custom/ca.crt\",\"tls_disable_system_cas\":true,\"base_url\":\"https://corp-llm-gateway.internal\",\"max_tokens\":1024,\"temperature\":0.7},\"description\":\"\",\"instruction\":\"You are a helpful assistant in a corporate environment.\",\"stream\":false}" } }, @@ -137,7 +137,7 @@ "template": { "metadata": { "annotations": { - "kagent.dev/config-hash": "16161564609559128349" + "kagent.dev/config-hash": "16193366991679050633" }, "labels": { "app": "kagent", diff --git a/ui/src/app/actions/servers.ts b/ui/src/app/actions/servers.ts index 7441187b0c..99183b09fb 100644 --- a/ui/src/app/actions/servers.ts +++ b/ui/src/app/actions/servers.ts @@ -18,7 +18,7 @@ export async function getServers(): Promise> return { message: "MCP servers fetched successfully", - data: response.data, + data: response.data ?? [], }; } catch (error) { return createErrorResponse(error, "Error getting MCP servers"); From c75de66acd4c00b476a062943442198e3d38044b Mon Sep 17 00:00:00 2001 From: Jet Chiang Date: Tue, 26 May 2026 11:40:13 -0400 Subject: [PATCH 4/8] fix lint, self review comments, etc Signed-off-by: Jet Chiang --- go/adk/examples/byo/main.go | 2 +- go/adk/pkg/a2a/agentcard.go | 2 +- go/adk/pkg/a2a/consts.go | 2 +- go/adk/pkg/a2a/converter.go | 2 +- go/adk/pkg/a2a/converter_test.go | 2 +- go/adk/pkg/a2a/executor.go | 2 +- go/core/internal/a2a/a2a_handler_mux.go | 7 ++++--- go/core/internal/a2a/a2a_registrar.go | 9 +++++++-- go/core/internal/a2a/passthrough_handler.go | 4 ++-- .../translator/agent/manifest_builder.go | 2 +- go/core/internal/database/client_postgres.go | 7 +++++++ .../internal/httpserver/handlers/sessions.go | 2 ++ go/core/internal/httpserver/handlers/tasks.go | 3 +++ go/core/internal/utils/a2a_version.go | 1 + go/core/pkg/a2acompat/trpcv0/convert.go | 20 ++++++++----------- 15 files changed, 41 insertions(+), 26 deletions(-) diff --git a/go/adk/examples/byo/main.go b/go/adk/examples/byo/main.go index 1de80c0c26..ff7ea1d54b 100644 --- a/go/adk/examples/byo/main.go +++ b/go/adk/examples/byo/main.go @@ -50,7 +50,7 @@ import ( "google.golang.org/adk/agent/llmagent" "google.golang.org/adk/agent/workflowagents/parallelagent" "google.golang.org/adk/runner" - "google.golang.org/adk/server/adka2a" + "google.golang.org/adk/server/adka2a" //nolint:staticcheck // TODO(0.11.0): migrate Go ADK runtime to adka2a/v2. adksession "google.golang.org/adk/session" ) diff --git a/go/adk/pkg/a2a/agentcard.go b/go/adk/pkg/a2a/agentcard.go index 6dfc7771be..f08306b3e9 100644 --- a/go/adk/pkg/a2a/agentcard.go +++ b/go/adk/pkg/a2a/agentcard.go @@ -3,7 +3,7 @@ package a2a import ( a2atype "github.com/a2aproject/a2a-go/a2a" adkagent "google.golang.org/adk/agent" - "google.golang.org/adk/server/adka2a" + "google.golang.org/adk/server/adka2a" //nolint:staticcheck // TODO(0.11.0): migrate Go ADK runtime to adka2a/v2. ) // EnrichAgentCard populates the agent card with skills derived from the ADK diff --git a/go/adk/pkg/a2a/consts.go b/go/adk/pkg/a2a/consts.go index 950b8cd1ef..ff7b138ade 100644 --- a/go/adk/pkg/a2a/consts.go +++ b/go/adk/pkg/a2a/consts.go @@ -1,6 +1,6 @@ package a2a -import "google.golang.org/adk/server/adka2a" +import "google.golang.org/adk/server/adka2a" //nolint:staticcheck // TODO(0.11.0): migrate Go ADK runtime to adka2a/v2. const ( StateKeySessionName = "session_name" diff --git a/go/adk/pkg/a2a/converter.go b/go/adk/pkg/a2a/converter.go index c74dfe7784..dcc9f48fa8 100644 --- a/go/adk/pkg/a2a/converter.go +++ b/go/adk/pkg/a2a/converter.go @@ -6,7 +6,7 @@ import ( "maps" a2atype "github.com/a2aproject/a2a-go/a2a" - "google.golang.org/adk/server/adka2a" + "google.golang.org/adk/server/adka2a" //nolint:staticcheck // TODO(0.11.0): migrate Go ADK runtime to adka2a/v2. adksession "google.golang.org/adk/session" "google.golang.org/genai" ) diff --git a/go/adk/pkg/a2a/converter_test.go b/go/adk/pkg/a2a/converter_test.go index 43df600982..133ab50f04 100644 --- a/go/adk/pkg/a2a/converter_test.go +++ b/go/adk/pkg/a2a/converter_test.go @@ -5,7 +5,7 @@ import ( "testing" a2atype "github.com/a2aproject/a2a-go/a2a" - "google.golang.org/adk/server/adka2a" + "google.golang.org/adk/server/adka2a" //nolint:staticcheck // TODO(0.11.0): migrate Go ADK runtime to adka2a/v2. "google.golang.org/genai" ) diff --git a/go/adk/pkg/a2a/executor.go b/go/adk/pkg/a2a/executor.go index 8ae11d3d69..e8826b58f0 100644 --- a/go/adk/pkg/a2a/executor.go +++ b/go/adk/pkg/a2a/executor.go @@ -19,7 +19,7 @@ import ( "go.opentelemetry.io/otel/attribute" adkagent "google.golang.org/adk/agent" "google.golang.org/adk/runner" - "google.golang.org/adk/server/adka2a" + "google.golang.org/adk/server/adka2a" //nolint:staticcheck // TODO(0.11.0): migrate Go ADK runtime to adka2a/v2. ) const ( diff --git a/go/core/internal/a2a/a2a_handler_mux.go b/go/core/internal/a2a/a2a_handler_mux.go index 3cbbadf50f..fee3400a86 100644 --- a/go/core/internal/a2a/a2a_handler_mux.go +++ b/go/core/internal/a2a/a2a_handler_mux.go @@ -3,6 +3,7 @@ package a2a import ( "fmt" "net/http" + "slices" "strings" "sync" @@ -59,7 +60,7 @@ func (a *handlerMux) SetAgentHandler( card a2atype.AgentCard, tracing middleware, ) error { - // TODO: Remove this in release 0.11.0 when all agents are migrated to v1 + // TODO(cleanup): Replace this protocol mux with the standard v1 handler stack once legacy clients/runtimes are unsupported. requestHandler := NewPassthroughRequestHandler(client, &card) legacyJSONRPCHandler := a2av0.NewJSONRPCHandler(requestHandler) v1JSONRPCHandler := a2asrv.NewJSONRPCHandler(requestHandler) @@ -89,8 +90,8 @@ func (a *handlerMux) SetAgentHandler( if tracing != nil { middlewares = append(middlewares, tracing) } - for i := len(middlewares) - 1; i >= 0; i-- { - handler = middlewares[i].Wrap(handler) + for _, middleware := range slices.Backward(middlewares) { + handler = middleware.Wrap(handler) } a.lock.Lock() diff --git a/go/core/internal/a2a/a2a_registrar.go b/go/core/internal/a2a/a2a_registrar.go index 7defe3d95f..f4d4133fe4 100644 --- a/go/core/internal/a2a/a2a_registrar.go +++ b/go/core/internal/a2a/a2a_registrar.go @@ -159,13 +159,15 @@ func (a *A2ARegistrar) upsertAgentHandler(ctx context.Context, agent v1alpha2.Ag httpClient := debugHTTPClient() client, err := a2aclient.NewFromEndpoints( ctx, - // TODO: Switch this to 1.0 in release 0.11.0 when all agents are migrated to v1 + // TODO(0.11.0): Prefer A2A 1.0 interfaces by default once managed runtimes are v1-capable. + // Keep legacy fallback during rollout so old agent pods continue to serve traffic. filterInterfacesByVersion(card.SupportedInterfaces, a2atype.ProtocolVersion("0.3")), a2aclient.WithJSONRPCTransport(httpClient), - // TODO: Remove this in release 0.11.0 when all agents are migrated to v1 + // TODO(cleanup): Remove the compat transport after legacy runtimes are unsupported. a2aclient.WithCompatTransport( a2atype.ProtocolVersion("0.3"), a2atype.TransportProtocolJSONRPC, + // This creates a legacy JSON-RPC transport that is used to forward traffic to agents that are still on the legacy A2A wire. a2aclient.TransportFactoryFn(func(_ context.Context, _ *a2atype.AgentCard, iface *a2atype.AgentInterface) (a2aclient.Transport, error) { return a2av0.NewJSONRPCTransport(a2av0.JSONRPCTransportConfig{ URL: iface.URL, @@ -224,6 +226,7 @@ func a2aRoutePath(agent v1alpha2.AgentObject) string { return routeKey(agent.GetWorkloadMode() == v1alpha2.WorkloadModeSandbox, agentRef.Namespace, agentRef.Name) } +// cloneInterfacesWithURL clones the interfaces and sets the URL to the given value. func cloneInterfacesWithURL(interfaces []*a2atype.AgentInterface, url string) []*a2atype.AgentInterface { if len(interfaces) == 0 { return []*a2atype.AgentInterface{ @@ -249,6 +252,8 @@ func cloneInterfacesWithURL(interfaces []*a2atype.AgentInterface, url string) [] return result } +// filterInterfacesByVersion filters the interfaces to only include the ones that match the given version. +// Currently, this is used to select the A2A 0.3 interface for managed agents. func filterInterfacesByVersion(interfaces []*a2atype.AgentInterface, version a2atype.ProtocolVersion) []*a2atype.AgentInterface { filtered := make([]*a2atype.AgentInterface, 0, len(interfaces)) for _, i := range interfaces { diff --git a/go/core/internal/a2a/passthrough_handler.go b/go/core/internal/a2a/passthrough_handler.go index 60a99263ef..3241b06460 100644 --- a/go/core/internal/a2a/passthrough_handler.go +++ b/go/core/internal/a2a/passthrough_handler.go @@ -9,6 +9,8 @@ import ( "github.com/a2aproject/a2a-go/v2/a2asrv" ) +// TODO(cleanup): once legacy traffic is unsupported, use the standard v1 handler +// stack directly. type PassthroughRequestHandler struct { client *a2aclient.Client card *a2atype.AgentCard @@ -21,8 +23,6 @@ var _ a2asrv.RequestHandler = (*PassthroughRequestHandler)(nil) // agent client and intentionally bypasses a2asrv.NewHandler, which would create // local task state and apply v1 task-processing invariants to legacy streams. // Keep this while the controller bridges mixed 0.3 and 1.0 clients/runtimes; -// once all supported traffic is native v1, the controller can use the standard -// v1 handler stack directly. func NewPassthroughRequestHandler(client *a2aclient.Client, card *a2atype.AgentCard) *PassthroughRequestHandler { return &PassthroughRequestHandler{ client: client, diff --git a/go/core/internal/controller/translator/agent/manifest_builder.go b/go/core/internal/controller/translator/agent/manifest_builder.go index befb8e43da..466edfe104 100644 --- a/go/core/internal/controller/translator/agent/manifest_builder.go +++ b/go/core/internal/controller/translator/agent/manifest_builder.go @@ -149,7 +149,7 @@ func (a *adkApiTranslator) buildConfigSecret( cfgJSON = string(bCfg) } if card != nil { - // TODO: replace this with the v1 agent card producer in release 0.11.0 + // TODO(0.11.0): use the v1 agent card producer once managed runtimes no longer need legacy top-level fields. producer := a2av0.NewStaticAgentCardProducer(card) jsonProducer, ok := producer.(a2asrv.AgentCardJSONProducer) if !ok { diff --git a/go/core/internal/database/client_postgres.go b/go/core/internal/database/client_postgres.go index 4fa3956c43..01ab1b7067 100644 --- a/go/core/internal/database/client_postgres.go +++ b/go/core/internal/database/client_postgres.go @@ -199,6 +199,8 @@ func (c *postgresClient) ListEventsForSession(ctx context.Context, sessionID, us // ── Tasks ───────────────────────────────────────────────────────────────────── +// TODO(0.11.0): Switch task writes to v1 storage format and remove legacy conversion from this write path. +// NOTE: We will still need to keep the read compatibility for legacy rows in 0.11.0 func (c *postgresClient) StoreTask(ctx context.Context, task *a2a.Task) error { legacyTask, err := trpcv0.ToLegacyTask(task) if err != nil { @@ -246,6 +248,8 @@ func (c *postgresClient) DeleteTask(ctx context.Context, taskID string) error { // ── Push Notifications ──────────────────────────────────────────────────────── +// TODO(0.11.0): Switch push notification writes to v1 storage format and remove legacy conversion from this write path. +// NOTE: We will still need to keep the read compatibility for legacy rows in 0.11.0. func (c *postgresClient) StorePushNotification(ctx context.Context, config *a2a.PushConfig) error { legacyConfig := trpcv0.ToLegacyPushConfig(config) data, err := json.Marshal(legacyConfig) @@ -742,6 +746,7 @@ func toEvent(r dbgen.Event) *dbpkg.Event { } } +//nolint:unused // Kept for parity with other row mappers and future raw task DB APIs. func toTask(r dbgen.Task) *dbpkg.Task { return &dbpkg.Task{ ID: r.ID, @@ -872,6 +877,7 @@ func strPtrIfNotEmpty(s string) *string { return &s } +// parseVersionedTask parses a task from a string and a version, handles conversion from legacy to v1 format. func parseVersionedTask(data string, version *string) (*a2a.Task, error) { switch { case version == nil || *version == "": @@ -895,6 +901,7 @@ func parseVersionedTask(data string, version *string) (*a2a.Task, error) { } } +// parseVersionedPushConfig parses a push notification config from a string and a version, handles conversion from legacy to v1 format. func parseVersionedPushConfig(data string, version *string) (*a2a.PushConfig, error) { switch { case version == nil || *version == "": diff --git a/go/core/internal/httpserver/handlers/sessions.go b/go/core/internal/httpserver/handlers/sessions.go index 0e985ed3ca..10e76a6327 100644 --- a/go/core/internal/httpserver/handlers/sessions.go +++ b/go/core/internal/httpserver/handlers/sessions.go @@ -354,6 +354,8 @@ func (h *SessionsHandler) HandleListTasksForSession(w ErrorResponseWriter, r *ht } log.Info("Successfully retrieved session tasks", "count", len(tasks)) + + // TODO(cleanup): Remove legacy API conversion after legacy wire support is no longer supported. switch wireVersion { case utils.A2AWireVersionLegacy: legacyTasks := make([]any, 0, len(tasks)) diff --git a/go/core/internal/httpserver/handlers/tasks.go b/go/core/internal/httpserver/handlers/tasks.go index 28b9d67426..eefdc3707b 100644 --- a/go/core/internal/httpserver/handlers/tasks.go +++ b/go/core/internal/httpserver/handlers/tasks.go @@ -45,6 +45,8 @@ func (h *TasksHandler) HandleGetTask(w ErrorResponseWriter, r *http.Request) { } log.Info("Successfully retrieved task") + // TODO(cleanup): Remove legacy API conversion after legacy wire support is no longer supported. + // Currently this will return either legacy or v1 task depending on the wire version var data any switch wireVersion { case utils.A2AWireVersionLegacy: @@ -74,6 +76,7 @@ func (h *TasksHandler) HandleCreateTask(w ErrorResponseWriter, r *http.Request) } task := a2a.Task{} + // TODO(cleanup): Remove legacy API conversion after legacy wire support is no longer supported. switch wireVersion { case utils.A2AWireVersionLegacy: legacyTask := protocol.Task{} diff --git a/go/core/internal/utils/a2a_version.go b/go/core/internal/utils/a2a_version.go index c6eaf05d2e..042d701a32 100644 --- a/go/core/internal/utils/a2a_version.go +++ b/go/core/internal/utils/a2a_version.go @@ -17,6 +17,7 @@ const ( // NegotiateA2AWireVersion returns the A2A wire version requested by the client. // Missing or explicit 0.3 headers use the legacy/current kagent A2A wire shape. +// TODO(cleanup): Revisit missing-header behavior once legacy wire clients are unsupported. func NegotiateA2AWireVersion(r *http.Request) (A2AWireVersion, error) { version := r.Header.Get(a2atype.SvcParamVersion) switch version { diff --git a/go/core/pkg/a2acompat/trpcv0/convert.go b/go/core/pkg/a2acompat/trpcv0/convert.go index 15f3629b26..dabc1ca0fe 100644 --- a/go/core/pkg/a2acompat/trpcv0/convert.go +++ b/go/core/pkg/a2acompat/trpcv0/convert.go @@ -175,13 +175,13 @@ func toLegacyMessage(message *a2av1.Message) (*trpc.Message, error) { result := &trpc.Message{ Kind: trpc.KindMessage, MessageID: message.ID, - ContextID: strPtr(contextID), + ContextID: new(contextID), Extensions: message.Extensions, Metadata: message.Metadata, Parts: parts, ReferenceTaskIDs: toLegacyTaskIDs(message.ReferenceTasks), Role: toLegacyMessageRole(message.Role), - TaskID: strPtr(taskID), + TaskID: new(taskID), } return result, nil } @@ -200,8 +200,8 @@ func toLegacyArtifact(artifact *a2av1.Artifact) (*trpc.Artifact, error) { return &trpc.Artifact{ ArtifactID: id, - Name: strPtr(name), - Description: strPtr(description), + Name: new(name), + Description: new(description), Metadata: artifact.Metadata, Extensions: artifact.Extensions, Parts: parts, @@ -248,8 +248,8 @@ func toLegacyPart(part *a2av1.Part) (trpc.Part, error) { return trpc.FilePart{ Kind: trpc.KindFile, File: &trpc.FileWithURI{ - Name: strPtr(fileName), - MimeType: strPtr(mimeType), + Name: new(fileName), + MimeType: new(mimeType), URI: urlString, }, Metadata: part.Metadata, @@ -262,8 +262,8 @@ func toLegacyPart(part *a2av1.Part) (trpc.Part, error) { return trpc.FilePart{ Kind: trpc.KindFile, File: &trpc.FileWithBytes{ - Name: strPtr(fileName), - MimeType: strPtr(mimeType), + Name: new(fileName), + MimeType: new(mimeType), Bytes: string(raw), }, Metadata: part.Metadata, @@ -328,10 +328,6 @@ func formatTimestamp(timestamp *time.Time) string { return timestamp.UTC().Format(time.RFC3339Nano) } -func strPtr(s string) *string { - return &s -} - func ToOfficialV0TaskStatus(status trpc.TaskStatus) (legacya2a.TaskStatus, error) { var msg *legacya2a.Message var err error From 2ca2fe9ff2a3ce5a4faec05288d7be3c8ec1ce19 Mon Sep 17 00:00:00 2001 From: Jet Chiang Date: Tue, 26 May 2026 12:53:29 -0400 Subject: [PATCH 5/8] FIX mcp unit test, missed upstream commit Signed-off-by: Jet Chiang --- go/core/internal/mcp/mcp_handler.go | 19 +++++++++++++++++-- go/core/pkg/env/kagent.go | 10 ++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/go/core/internal/mcp/mcp_handler.go b/go/core/internal/mcp/mcp_handler.go index ff3d03b14f..d65473f9b1 100644 --- a/go/core/internal/mcp/mcp_handler.go +++ b/go/core/internal/mcp/mcp_handler.go @@ -12,10 +12,12 @@ import ( a2atype "github.com/a2aproject/a2a-go/v2/a2a" a2aclientv2 "github.com/a2aproject/a2a-go/v2/a2aclient" "github.com/a2aproject/a2a-go/v2/a2aclient/agentcard" + "github.com/google/jsonschema-go/jsonschema" "github.com/kagent-dev/kagent/go/api/v1alpha2" "github.com/kagent-dev/kagent/go/core/internal/a2a" "github.com/kagent-dev/kagent/go/core/internal/version" "github.com/kagent-dev/kagent/go/core/pkg/auth" + "github.com/kagent-dev/kagent/go/core/pkg/env" mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -82,12 +84,21 @@ func NewMCPHandler(kubeClient client.Client, a2aBaseURL string, authenticator au server := mcpsdk.NewServer(impl, nil) handler.server = server - // Add list_agents tool + // Add list_agents tool. + // InputSchema is set explicitly (rather than reflected from the empty + // ListAgentsInput struct) so the serialized schema includes "properties": {}. + // OpenAI strict mode rejects object schemas without a properties key. + // See https://github.com/kagent-dev/kagent/issues/1889. mcpsdk.AddTool[ListAgentsInput, ListAgentsOutput]( server, &mcpsdk.Tool{ Name: "list_agents", Description: "List invokable kagent agents (accepted + deploymentReady)", + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{}, + AdditionalProperties: &jsonschema.Schema{Not: &jsonschema.Schema{}}, + }, }, handler.handleListAgents, ) @@ -103,11 +114,15 @@ func NewMCPHandler(kubeClient client.Client, a2aBaseURL string, authenticator au ) // Create HTTP handler + var httpOpts *mcpsdk.StreamableHTTPOptions + if env.KagentMCPStateless.Get() { + httpOpts = &mcpsdk.StreamableHTTPOptions{Stateless: true} + } handler.httpHandler = mcpsdk.NewStreamableHTTPHandler( func(*http.Request) *mcpsdk.Server { return server }, - nil, + httpOpts, ) return handler, nil diff --git a/go/core/pkg/env/kagent.go b/go/core/pkg/env/kagent.go index be8c62d417..5d158b2060 100644 --- a/go/core/pkg/env/kagent.go +++ b/go/core/pkg/env/kagent.go @@ -23,6 +23,16 @@ var ( ComponentController, ) + KagentMCPStateless = RegisterBoolVar( + "KAGENT_MCP_STATELESS", + false, + "When true, the MCP server operates in stateless mode (no session persistence). "+ + "Use when the network path does not provide sticky session routing based on the Mcp-Session-Id header. "+ + "Note: stateless mode disables server-initiated notifications; clients will not receive "+ + "resources/updated events.", + ComponentController, + ) + // Variables injected into agent pods (not read by the controller itself). KagentName = RegisterStringVar( From 1c28fd957fc3b3217b294e7553ea74f43069c052 Mon Sep 17 00:00:00 2001 From: Jet Chiang Date: Tue, 26 May 2026 14:29:42 -0400 Subject: [PATCH 6/8] FIX remaining outdated changes in go/core Signed-off-by: Jet Chiang --- .../internal/a2a/client_interceptors_test.go | 56 ++++++++++++ go/core/internal/a2a/trace.go | 4 +- .../internal/httpserver/handlers/agents.go | 46 ++++++---- .../httpserver/handlers/agents_test.go | 86 +++++++++++++++++++ go/core/test/e2e/invoke_api_test.go | 18 +++- 5 files changed, 189 insertions(+), 21 deletions(-) create mode 100644 go/core/internal/a2a/client_interceptors_test.go diff --git a/go/core/internal/a2a/client_interceptors_test.go b/go/core/internal/a2a/client_interceptors_test.go new file mode 100644 index 0000000000..bf76addd6c --- /dev/null +++ b/go/core/internal/a2a/client_interceptors_test.go @@ -0,0 +1,56 @@ +package a2a + +import ( + "context" + "testing" + + a2aclient "github.com/a2aproject/a2a-go/v2/a2aclient" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" + "k8s.io/apimachinery/pkg/types" +) + +func TestUpstreamAuthInterceptor_InjectsTraceContext(t *testing.T) { + const rawTraceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" + + ctx := propagation.TraceContext{}.Extract( + context.Background(), + propagation.MapCarrier{"traceparent": rawTraceparent}, + ) + + req := &a2aclient.Request{ + BaseURL: "http://agent.default:8080", + ServiceParams: a2aclient.ServiceParams{}, + } + interceptor := NewUpstreamAuthInterceptor(nil, types.NamespacedName{}) + if _, _, err := interceptor.Before(ctx, req); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + gotValues := req.ServiceParams.Get("traceparent") + if len(gotValues) == 0 { + t.Fatal("expected traceparent service param on outgoing request, got none") + } + + outCtx := propagation.TraceContext{}.Extract(context.Background(), propagation.MapCarrier{"traceparent": gotValues[0]}) + wantTraceID := trace.SpanContextFromContext(ctx).TraceID() + gotTraceID := trace.SpanContextFromContext(outCtx).TraceID() + if wantTraceID != gotTraceID { + t.Errorf("trace ID: want %s, got %s", wantTraceID, gotTraceID) + } +} + +func TestUpstreamAuthInterceptor_NoTraceContext(t *testing.T) { + req := &a2aclient.Request{ + BaseURL: "http://agent.default:8080", + ServiceParams: a2aclient.ServiceParams{}, + } + interceptor := NewUpstreamAuthInterceptor(nil, types.NamespacedName{}) + if _, _, err := interceptor.Before(context.Background(), req); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if got := req.ServiceParams.Get("traceparent"); len(got) != 0 { + t.Errorf("expected no traceparent service param, got %q", got) + } +} diff --git a/go/core/internal/a2a/trace.go b/go/core/internal/a2a/trace.go index 81428a3d57..943407e1d8 100644 --- a/go/core/internal/a2a/trace.go +++ b/go/core/internal/a2a/trace.go @@ -16,8 +16,8 @@ import ( // a2aTracingMiddleware is an A2A server middleware that creates an invoke_agent // span for each inbound A2A request, annotated with GenAI semantic convention -// attributes. The span becomes the parent of any outbound proxy calls made by -// traceInjectHandler, giving a clean agent-invocation span hierarchy in Jaeger. +// attributes. Outbound client interceptors inject that span into proxied agent +// calls, giving a clean agent-invocation span hierarchy in Jaeger. type a2aTracingMiddleware struct { agentRef types.NamespacedName provider attribute.KeyValue diff --git a/go/core/internal/httpserver/handlers/agents.go b/go/core/internal/httpserver/handlers/agents.go index 25806f3b60..59c68ce27f 100644 --- a/go/core/internal/httpserver/handlers/agents.go +++ b/go/core/internal/httpserver/handlers/agents.go @@ -18,6 +18,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + utilvalidation "k8s.io/apimachinery/pkg/util/validation" "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -32,35 +33,46 @@ func NewAgentsHandler(base *Base) *AgentsHandler { return &AgentsHandler{Base: base} } -// HandleListAgents handles GET /api/agents requests using database +// HandleListAgents handles GET /api/agents requests using database. +// Optional query param: namespace=. func (h *AgentsHandler) HandleListAgents(w ErrorResponseWriter, r *http.Request) { log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "list-db") - if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent"}); err != nil { - w.RespondWithError(err) + namespace := r.URL.Query().Get("namespace") + if namespace == "" { + h.handleListAgents(w, r, log) return } - agentList := &v1alpha2.AgentList{} - if err := h.KubeClient.List(r.Context(), agentList); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to list Agents from Kubernetes", err)) + if strings.TrimSpace(namespace) != namespace { + w.RespondWithError(errors.NewBadRequestError( + fmt.Sprintf("invalid namespace %q: must not contain leading or trailing whitespace", namespace), + nil, + )) return } - agentsWithID := make([]api.AgentResponse, 0) - h.appendAgentResponses(r.Context(), log, agentObjects(agentList.Items), &agentsWithID) + if errs := utilvalidation.IsDNS1123Label(namespace); len(errs) > 0 { + w.RespondWithError(errors.NewBadRequestError( + fmt.Sprintf("invalid namespace %q: %s", namespace, strings.Join(errs, "; ")), + nil, + )) + return + } - harnessList := &v1alpha2.AgentHarnessList{} - if err := h.KubeClient.List(r.Context(), harnessList); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to list AgentHarness resources from Kubernetes", err)) + h.handleListAgents(w, r, log.WithValues("namespace", namespace), client.InNamespace(namespace)) +} + +func (h *AgentsHandler) handleListAgents(w ErrorResponseWriter, r *http.Request, log logr.Logger, opts ...client.ListOption) { + if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent"}); err != nil { + w.RespondWithError(err) return } - for i := range harnessList.Items { - sb := &harnessList.Items[i] - if sb.Spec.Backend != v1alpha2.AgentHarnessBackendOpenClaw && sb.Spec.Backend != v1alpha2.AgentHarnessBackendNemoClaw { - continue - } - agentsWithID = append(agentsWithID, h.openshellAgentHarnessAgentResponse(r.Context(), log, sb)) + + agentsWithID, err := h.listAgentResponses(r.Context(), log, opts...) + if err != nil { + w.RespondWithError(err) + return } log.Info("Successfully listed agents", "count", len(agentsWithID)) diff --git a/go/core/internal/httpserver/handlers/agents_test.go b/go/core/internal/httpserver/handlers/agents_test.go index e16da1bd7e..efe2bf352c 100644 --- a/go/core/internal/httpserver/handlers/agents_test.go +++ b/go/core/internal/httpserver/handlers/agents_test.go @@ -500,6 +500,92 @@ func TestHandleListAgents(t *testing.T) { } require.True(t, found) }) + + t.Run("filters Agent and AgentHarness rows by namespace query parameter", func(t *testing.T) { + modelConfig := createTestModelConfig() + agentDefault := createTestAgent("agent-in-default", modelConfig) + agentOther := &v1alpha2.Agent{ + ObjectMeta: metav1.ObjectMeta{Name: "agent-in-other", Namespace: "other"}, + Spec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_Declarative, + Declarative: &v1alpha2.DeclarativeAgentSpec{ + ModelConfig: modelConfig.Name, + }, + }, + } + harnessDefault := &v1alpha2.AgentHarness{ + ObjectMeta: metav1.ObjectMeta{Name: "harness-default", Namespace: "default"}, + Spec: v1alpha2.AgentHarnessSpec{ + Backend: v1alpha2.AgentHarnessBackendOpenClaw, + ModelConfigRef: "test-model-config", + }, + } + harnessOther := &v1alpha2.AgentHarness{ + ObjectMeta: metav1.ObjectMeta{Name: "harness-other", Namespace: "other"}, + Spec: v1alpha2.AgentHarnessSpec{ + Backend: v1alpha2.AgentHarnessBackendOpenClaw, + ModelConfigRef: "test-model-config", + }, + } + unsupportedHarnessDefault := &v1alpha2.AgentHarness{ + ObjectMeta: metav1.ObjectMeta{Name: "unsupported-harness", Namespace: "default"}, + Spec: v1alpha2.AgentHarnessSpec{ + Backend: v1alpha2.AgentHarnessBackendType("unsupported"), + ModelConfigRef: "test-model-config", + }, + } + handler, _ := setupTestHandler(t, agentDefault, agentOther, harnessDefault, harnessOther, unsupportedHarnessDefault, modelConfig) + + req := httptest.NewRequest("GET", "/api/agents?namespace=default", nil) + req = setUser(req, "test-user") + w := httptest.NewRecorder() + + handler.HandleListAgents(&testErrorResponseWriter{w}, req) + + require.Equal(t, http.StatusOK, w.Code) + var response api.StandardResponse[[]api.AgentResponse] + require.NoError(t, json.Unmarshal(w.Body.Bytes(), &response)) + require.Len(t, response.Data, 2) + + byName := make(map[string]api.AgentResponse, len(response.Data)) + for _, row := range response.Data { + byName[row.Agent.Metadata.Name] = row + require.Equal(t, "default", row.Agent.Metadata.Namespace) + } + require.Contains(t, byName, "agent-in-default") + require.Contains(t, byName, "harness-default") + require.NotContains(t, byName, "agent-in-other") + require.NotContains(t, byName, "harness-other") + require.NotContains(t, byName, "unsupported-harness") + }) + + // Kubernetes namespace names must be DNS-1123 labels. Rejecting invalid input + // before calling the Kubernetes client keeps the list path consistent with + // other resource handlers and avoids surprising cross-namespace behavior. + t.Run("returns 400 for invalid namespace query value", func(t *testing.T) { + handler, _ := setupTestHandler(t) + + req := httptest.NewRequest("GET", "/api/agents?namespace=INVALID_NS!", nil) + req = setUser(req, "test-user") + w := httptest.NewRecorder() + + handler.HandleListAgents(&testErrorResponseWriter{w}, req) + + require.Equal(t, http.StatusBadRequest, w.Code) + }) + + t.Run("returns 400 for namespace query value with leading or trailing whitespace", func(t *testing.T) { + handler, _ := setupTestHandler(t) + + req := httptest.NewRequest("GET", "/api/agents?namespace=%20default", nil) + req = setUser(req, "test-user") + w := httptest.NewRecorder() + + handler.HandleListAgents(&testErrorResponseWriter{w}, req) + + require.Equal(t, http.StatusBadRequest, w.Code) + require.Contains(t, w.Body.String(), "must not contain leading or trailing whitespace") + }) } func TestHandleListSandboxAgents(t *testing.T) { diff --git a/go/core/test/e2e/invoke_api_test.go b/go/core/test/e2e/invoke_api_test.go index 271eff8db7..3f4452ce20 100644 --- a/go/core/test/e2e/invoke_api_test.go +++ b/go/core/test/e2e/invoke_api_test.go @@ -1075,6 +1075,15 @@ func TestE2EInvokeCrewAIAgent(t *testing.T) { } func TestE2EInvokeSTSIntegration(t *testing.T) { + runE2EInvokeSTSIntegration(t, "python", nil) +} + +func TestE2EGoInvokeSTSIntegration(t *testing.T) { + goRuntime := v1alpha2.DeclarativeRuntime_Go + runE2EInvokeSTSIntegration(t, "go", &goRuntime) +} + +func runE2EInvokeSTSIntegration(t *testing.T, runtimeName string, runtimeOverride *v1alpha2.DeclarativeRuntime) { // Setup mock STS server agentName := "test-sts" agentServiceAccount := fmt.Sprintf("system:serviceaccount:kagent:%s", agentName) @@ -1110,8 +1119,9 @@ func TestE2EInvokeSTSIntegration(t *testing.T) { modelCfg := setupModelConfig(t, cli, baseURL) agent := setupAgentWithOptions(t, cli, modelCfg.Name, tools, AgentOptions{ - Name: "test-sts-agent", + Name: "test-sts-agent-" + runtimeName, SystemMessage: "You are an agent that adds numbers using the add tool available to you through the everything-mcp-server.", + Runtime: runtimeOverride, Env: []corev1.EnvVar{ { Name: "STS_WELL_KNOWN_URI", @@ -1139,7 +1149,7 @@ func TestE2EInvokeSTSIntegration(t *testing.T) { a2aURL := a2aUrl(agent.Namespace, agent.Name) a2aClient := newA2AClient(t, a2aURL, httpClient, nil) - t.Run("sync_invocation", func(t *testing.T) { + t.Run(runtimeName+"/sts_exchange_sync_invocation", func(t *testing.T) { runSyncTest(t, a2aClient, "add 3 and 5", "8", nil) // verify our mock STS server received the token exchange request @@ -1150,6 +1160,10 @@ func TestE2EInvokeSTSIntegration(t *testing.T) { // which contains the may act claim stsRequest := stsRequests[0] require.Equal(t, subjectToken, stsRequest.SubjectToken) + require.Equal(t, "urn:ietf:params:oauth:grant-type:token-exchange", stsRequest.GrantType) + require.Equal(t, "urn:ietf:params:oauth:token-type:jwt", stsRequest.SubjectTokenType) + require.NotEmpty(t, stsRequest.ActorToken) + require.Equal(t, "urn:ietf:params:oauth:token-type:jwt", stsRequest.ActorTokenType) }) } From 5ff362ccb6b522cb637b0fa6be2c1006338f4122 Mon Sep 17 00:00:00 2001 From: Jet Chiang Date: Tue, 26 May 2026 15:07:18 -0400 Subject: [PATCH 7/8] some fixes more Signed-off-by: Jet Chiang --- go/adk/examples/byo/main.go | 2 +- go/adk/pkg/a2a/agentcard.go | 2 +- go/adk/pkg/a2a/consts.go | 2 +- go/adk/pkg/a2a/converter.go | 2 +- go/adk/pkg/a2a/converter_test.go | 2 +- go/adk/pkg/a2a/executor.go | 2 +- go/go.mod | 4 ++-- go/go.sum | 4 ++-- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/go/adk/examples/byo/main.go b/go/adk/examples/byo/main.go index ff7ea1d54b..1de80c0c26 100644 --- a/go/adk/examples/byo/main.go +++ b/go/adk/examples/byo/main.go @@ -50,7 +50,7 @@ import ( "google.golang.org/adk/agent/llmagent" "google.golang.org/adk/agent/workflowagents/parallelagent" "google.golang.org/adk/runner" - "google.golang.org/adk/server/adka2a" //nolint:staticcheck // TODO(0.11.0): migrate Go ADK runtime to adka2a/v2. + "google.golang.org/adk/server/adka2a" adksession "google.golang.org/adk/session" ) diff --git a/go/adk/pkg/a2a/agentcard.go b/go/adk/pkg/a2a/agentcard.go index f08306b3e9..6dfc7771be 100644 --- a/go/adk/pkg/a2a/agentcard.go +++ b/go/adk/pkg/a2a/agentcard.go @@ -3,7 +3,7 @@ package a2a import ( a2atype "github.com/a2aproject/a2a-go/a2a" adkagent "google.golang.org/adk/agent" - "google.golang.org/adk/server/adka2a" //nolint:staticcheck // TODO(0.11.0): migrate Go ADK runtime to adka2a/v2. + "google.golang.org/adk/server/adka2a" ) // EnrichAgentCard populates the agent card with skills derived from the ADK diff --git a/go/adk/pkg/a2a/consts.go b/go/adk/pkg/a2a/consts.go index ff7b138ade..950b8cd1ef 100644 --- a/go/adk/pkg/a2a/consts.go +++ b/go/adk/pkg/a2a/consts.go @@ -1,6 +1,6 @@ package a2a -import "google.golang.org/adk/server/adka2a" //nolint:staticcheck // TODO(0.11.0): migrate Go ADK runtime to adka2a/v2. +import "google.golang.org/adk/server/adka2a" const ( StateKeySessionName = "session_name" diff --git a/go/adk/pkg/a2a/converter.go b/go/adk/pkg/a2a/converter.go index dcc9f48fa8..c74dfe7784 100644 --- a/go/adk/pkg/a2a/converter.go +++ b/go/adk/pkg/a2a/converter.go @@ -6,7 +6,7 @@ import ( "maps" a2atype "github.com/a2aproject/a2a-go/a2a" - "google.golang.org/adk/server/adka2a" //nolint:staticcheck // TODO(0.11.0): migrate Go ADK runtime to adka2a/v2. + "google.golang.org/adk/server/adka2a" adksession "google.golang.org/adk/session" "google.golang.org/genai" ) diff --git a/go/adk/pkg/a2a/converter_test.go b/go/adk/pkg/a2a/converter_test.go index 133ab50f04..43df600982 100644 --- a/go/adk/pkg/a2a/converter_test.go +++ b/go/adk/pkg/a2a/converter_test.go @@ -5,7 +5,7 @@ import ( "testing" a2atype "github.com/a2aproject/a2a-go/a2a" - "google.golang.org/adk/server/adka2a" //nolint:staticcheck // TODO(0.11.0): migrate Go ADK runtime to adka2a/v2. + "google.golang.org/adk/server/adka2a" "google.golang.org/genai" ) diff --git a/go/adk/pkg/a2a/executor.go b/go/adk/pkg/a2a/executor.go index e8826b58f0..8ae11d3d69 100644 --- a/go/adk/pkg/a2a/executor.go +++ b/go/adk/pkg/a2a/executor.go @@ -19,7 +19,7 @@ import ( "go.opentelemetry.io/otel/attribute" adkagent "google.golang.org/adk/agent" "google.golang.org/adk/runner" - "google.golang.org/adk/server/adka2a" //nolint:staticcheck // TODO(0.11.0): migrate Go ADK runtime to adka2a/v2. + "google.golang.org/adk/server/adka2a" ) const ( diff --git a/go/go.mod b/go/go.mod index feb643b0a6..4706abeaaf 100644 --- a/go/go.mod +++ b/go/go.mod @@ -47,7 +47,7 @@ require ( go.uber.org/goleak v1.3.0 go.uber.org/zap v1.28.0 golang.org/x/text v0.37.0 - google.golang.org/adk v1.3.0 + google.golang.org/adk v1.2.0 google.golang.org/genai v1.57.0 google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af gopkg.in/yaml.v3 v3.0.1 @@ -63,6 +63,7 @@ require ( require ( github.com/aws/aws-sdk-go-v2 v1.41.7 github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.6 + github.com/golang-jwt/jwt/v5 v5.3.1 github.com/golang/protobuf v1.5.4 github.com/google/go-cmp v0.7.0 github.com/google/jsonschema-go v0.4.3 @@ -223,7 +224,6 @@ require ( github.com/goccy/go-json v0.10.3 // indirect github.com/godoc-lint/godoc-lint v0.11.2 // indirect github.com/gofrs/flock v0.13.0 // indirect - github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golangci/asciicheck v0.5.0 // indirect github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 // indirect github.com/golangci/go-printf-func-name v0.1.1 // indirect diff --git a/go/go.sum b/go/go.sum index 096e85b5e0..d9ebf2cc37 100644 --- a/go/go.sum +++ b/go/go.sum @@ -1027,8 +1027,8 @@ gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0 gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/adk v1.3.0 h1:paUr9uM2qANnMUAQ4ydMXMCnM1HtymhDYl8y7gnKvqs= -google.golang.org/adk v1.3.0/go.mod h1:R8tNFnI/eiBXHn7zJPJtqdiK/WXC+tVkyuZsXyNZXN4= +google.golang.org/adk v1.2.0 h1:MfQD1/GqPfIsFNBcozNykkjdqNIdCrPH/SNqKPZF/yM= +google.golang.org/adk v1.2.0/go.mod h1:6QY5jQI7awU4WYtJqvyIkJQheCvqsGWweU6BX63USEc= google.golang.org/api v0.279.0 h1:hsx2M2OaRcaKtVYK6vXEUnQvdjnend7ZYES+lYaot74= google.golang.org/api v0.279.0/go.mod h1:B9TqLBwJqVjp1mtt7WeoQwWRwvu/400y5lETOql+giQ= google.golang.org/genai v1.57.0 h1:qTyG2ynz5dQy2jF4CvZdLHHVslhR0heMue+zM1a4GNM= From 6350578745343c260febb1d1d62ace161cb6b6f1 Mon Sep 17 00:00:00 2001 From: Jet Chiang Date: Tue, 26 May 2026 15:18:09 -0400 Subject: [PATCH 8/8] fix a test on mac synlinks Signed-off-by: Jet Chiang --- go/adk/pkg/tools/skills_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/go/adk/pkg/tools/skills_test.go b/go/adk/pkg/tools/skills_test.go index 1e4758c9ef..f22a54aecd 100644 --- a/go/adk/pkg/tools/skills_test.go +++ b/go/adk/pkg/tools/skills_test.go @@ -21,8 +21,12 @@ func TestResolveReadPath_AllowsSymlinkedSkillsDirectory(t *testing.T) { if err != nil { t.Fatalf("resolveReadPath() error = %v", err) } - if resolved != skillFile { - t.Fatalf("resolveReadPath() = %q, want %q", resolved, skillFile) + want, err := filepath.EvalSymlinks(skillFile) + if err != nil { + t.Fatalf("EvalSymlinks() error = %v", err) + } + if resolved != want { + t.Fatalf("resolveReadPath() = %q, want %q", resolved, want) } }