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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions go/adk/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,20 +186,19 @@ func main() {
logger.Info("Memory service enabled", "appName", appName)
}

runnerConfig, subagentSessionIDs, err := runnerpkg.CreateRunnerConfig(ctx, agentConfig, sessionService, appName, memoryService, kagentURL, httpClient)
runnerConfig, err := runnerpkg.CreateRunnerConfig(ctx, agentConfig, sessionService, appName, memoryService, kagentURL, httpClient)
if err != nil {
logger.Error(err, "Failed to create Google ADK Runner config")
os.Exit(1)
}

stream := agentConfig.GetStream()
executor := a2a.NewKAgentExecutor(a2a.KAgentExecutorConfig{
RunnerConfig: runnerConfig,
SubagentSessionIDs: subagentSessionIDs,
SessionService: sessionService,
Stream: stream,
AppName: appName,
Logger: logger,
RunnerConfig: runnerConfig,
SessionService: sessionService,
Stream: stream,
AppName: appName,
Logger: logger,
})

// Build the agent card.
Expand Down
2 changes: 1 addition & 1 deletion go/adk/examples/oneshot/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func main() {
agentConfig.Stream = &t
}

adkAgent, err := agent.CreateGoogleADKAgent(ctx, agentConfig, "oneshot")
adkAgent, err := agent.CreateGoogleADKAgent(ctx, agentConfig, "oneshot", nil)
if err != nil {
fmt.Fprintf(os.Stderr, "error creating agent: %v\n", err)
os.Exit(1)
Expand Down
38 changes: 0 additions & 38 deletions go/adk/pkg/a2a/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,44 +114,6 @@ func convertDataPartToGenAI(p *a2atype.DataPart, typeKey string) (*genai.Part, e
return adka2a.ToGenAIPart(p)
}

// stampSubagentSessionID adds kagent_subagent_session_id to function_call
// DataParts when the tool name is present in subagentSessionIDs.
// Part can be either a *a2atype.DataPart or a2atype.DataPart.
func stampSubagentSessionID(part a2atype.Part, subagentSessionIDs map[string]string) a2atype.Part {
switch p := part.(type) {
case *a2atype.DataPart:
cp := *p
stampSubagentSessionIDOnDataPart(&cp, subagentSessionIDs)
return cp
case a2atype.DataPart:
cp := p
stampSubagentSessionIDOnDataPart(&cp, subagentSessionIDs)
return cp
default:
return part
}
}

func stampSubagentSessionIDOnDataPart(dp *a2atype.DataPart, subagentSessionIDs map[string]string) {
if dp == nil || len(subagentSessionIDs) == 0 {
return
}
if dp.Metadata == nil {
dp.Metadata = map[string]any{}
}
partType, _ := ReadMetadataValue(dp.Metadata, A2ADataPartMetadataTypeKey)
if partType != A2ADataPartMetadataTypeFunctionCall {
return
}
toolName, _ := dp.Data[PartKeyName].(string)
if toolName == "" {
return
}
if sessionID, ok := subagentSessionIDs[toolName]; ok && sessionID != "" {
dp.Metadata[GetKAgentMetadataKey("subagent_session_id")] = sessionID
}
}

// toA2AMetadataMap converts v to map[string]any via JSON so values placed in A2A
func toA2AMetadataMap(v any) (map[string]any, error) {
if v == nil {
Expand Down
53 changes: 0 additions & 53 deletions go/adk/pkg/a2a/converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,59 +199,6 @@ func TestMessageToGenAIContent_NilMessage(t *testing.T) {
}
}

// ---------------------------------------------------------------------------
// stampSubagentSessionID
// ---------------------------------------------------------------------------

func TestStampSubagentSessionID_FunctionCallPart(t *testing.T) {
subagentIDs := map[string]string{"k8s_agent": "session-abc"}

dp := &a2atype.DataPart{
Data: map[string]any{
PartKeyName: "k8s_agent",
PartKeyArgs: map[string]any{"request": "list pods"},
},
Metadata: map[string]any{
adka2a.ToA2AMetaKey("type"): A2ADataPartMetadataTypeFunctionCall,
},
}
updated := stampSubagentSessionID(dp, subagentIDs)
updatedDP, ok := updated.(a2atype.DataPart)
if !ok {
t.Fatalf("updated part type = %T, want a2atype.DataPart", updated)
}

sessionID, has := updatedDP.Metadata[GetKAgentMetadataKey("subagent_session_id")]
if !has {
t.Fatal("expected kagent_subagent_session_id in metadata, not found")
}
if sessionID != "session-abc" {
t.Errorf("session_id = %q, want session-abc", sessionID)
}
}

func TestStampSubagentSessionID_UnknownTool(t *testing.T) {
subagentIDs := map[string]string{"k8s_agent": "session-abc"}

dp := &a2atype.DataPart{
Data: map[string]any{
PartKeyName: "unknown_tool",
},
Metadata: map[string]any{
adka2a.ToA2AMetaKey("type"): A2ADataPartMetadataTypeFunctionCall,
},
}
updated := stampSubagentSessionID(dp, subagentIDs)
updatedDP, ok := updated.(a2atype.DataPart)
if !ok {
t.Fatalf("updated part type = %T, want a2atype.DataPart", updated)
}

if _, ok := updatedDP.Metadata[GetKAgentMetadataKey("subagent_session_id")]; ok {
t.Error("expected no subagent_session_id for unknown tool")
}
}

// ---------------------------------------------------------------------------
// toA2AMetadataMap
// ---------------------------------------------------------------------------
Expand Down
46 changes: 18 additions & 28 deletions go/adk/pkg/a2a/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,22 @@ const (

// KAgentExecutorConfig holds the configuration for KAgentExecutor
type KAgentExecutorConfig struct {
RunnerConfig runner.Config
SubagentSessionIDs map[string]string
SessionService adksession.Service
Stream bool
AppName string
SkillsDirectory string
Logger logr.Logger
RunnerConfig runner.Config
SessionService adksession.Service
Stream bool
AppName string
SkillsDirectory string
Logger logr.Logger
}

// KAgentExecutor implements a2asrv.AgentExecutor
type KAgentExecutor struct {
runnerConfig runner.Config
subagentSessionIDs map[string]string
sessionService adksession.Service
stream bool
appName string
skillsDirectory string
logger logr.Logger
runnerConfig runner.Config
sessionService adksession.Service
stream bool
appName string
skillsDirectory string
logger logr.Logger
}

var _ a2asrv.AgentExecutor = (*KAgentExecutor)(nil)
Expand All @@ -62,13 +60,12 @@ func NewKAgentExecutor(cfg KAgentExecutorConfig) *KAgentExecutor {
skillsDir = defaultSkillsDirectory
}
return &KAgentExecutor{
runnerConfig: cfg.RunnerConfig,
subagentSessionIDs: cfg.SubagentSessionIDs,
sessionService: cfg.SessionService,
stream: cfg.Stream,
appName: cfg.AppName,
skillsDirectory: skillsDir,
logger: cfg.Logger.WithName("kagent-executor"),
runnerConfig: cfg.RunnerConfig,
sessionService: cfg.SessionService,
stream: cfg.Stream,
appName: cfg.AppName,
skillsDirectory: skillsDir,
logger: cfg.Logger.WithName("kagent-executor"),
}
}

Expand Down Expand Up @@ -218,9 +215,6 @@ func (e *KAgentExecutor) Execute(ctx context.Context, reqCtx *a2asrv.RequestCont
return fmt.Errorf("inbound message conversion failed: %w", err)
}

// 7. Use pre-built subagent session ID map (built by runner bundle).
subagentSessionIDs := e.subagentSessionIDs

// 8. Create runner.
r, err := runner.New(e.runnerConfig)
if err != nil {
Expand Down Expand Up @@ -328,10 +322,6 @@ func (e *KAgentExecutor) Execute(ctx context.Context, reqCtx *a2asrv.RequestCont
if isEmptyDataPart(a2aPart) {
continue
}
// Stamp kagent_subagent_session_id onto function_call DataParts.
if len(subagentSessionIDs) > 0 {
a2aPart = stampSubagentSessionID(a2aPart, subagentSessionIDs)
}
a2aParts = append(a2aParts, a2aPart)
}

Expand Down
34 changes: 11 additions & 23 deletions go/adk/pkg/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,13 @@ const (
// CreateGoogleADKAgent creates a Google ADK agent from AgentConfig.
// agentName is used as the ADK agent identity (appears in event Author field).
// extraTools are appended to the agent's tool list (e.g. save_memory).
func CreateGoogleADKAgent(ctx context.Context, agentConfig *adk.AgentConfig, agentName string, extraTools ...tool.Tool) (agent.Agent, error) {
a, _, err := CreateGoogleADKAgentWithSubagentSessionIDs(ctx, agentConfig, agentName, nil, extraTools...)
return a, err
}

// CreateGoogleADKAgentWithSubagentSessionIDs creates a Google ADK agent and a
// map of remote-subagent tool name → A2A context session ID (for stamping
// outbound A2A events). Callers that only need the agent can use
// CreateGoogleADKAgent.
// Optional stsPlugin can be provided for token propagation to MCP tools.
func CreateGoogleADKAgentWithSubagentSessionIDs(ctx context.Context, agentConfig *adk.AgentConfig, agentName string, stsPlugin *sts.TokenPropagationPlugin, extraTools ...tool.Tool) (agent.Agent, map[string]string, error) {
// Optional stsPlugin can be provided for token propagation to MCP tools; pass
// nil if token propagation is not needed.
func CreateGoogleADKAgent(ctx context.Context, agentConfig *adk.AgentConfig, agentName string, stsPlugin *sts.TokenPropagationPlugin, extraTools ...tool.Tool) (agent.Agent, error) {
log := logr.FromContextOrDiscard(ctx)

if agentConfig == nil {
return nil, nil, fmt.Errorf("agent config is required")
return nil, fmt.Errorf("agent config is required")
}

propagateToken := strings.ToLower(os.Getenv("KAGENT_PROPAGATE_TOKEN")) == "true"
Expand All @@ -57,37 +49,33 @@ func CreateGoogleADKAgentWithSubagentSessionIDs(ctx context.Context, agentConfig
}
toolsets := mcp.CreateToolsets(ctx, agentConfig.HttpTools, agentConfig.SseTools, propagateToken, dynamicHeaderProvider)
mcpAppToolNames := mcp.MCPAppToolNamesFromToolsets(toolsets)
subagentSessionIDs := make(map[string]string)

var remoteAgentTools []tool.Tool
for _, remoteAgent := range agentConfig.RemoteAgents {
if remoteAgent.Url == "" {
log.Info("Skipping remote agent with empty URL", "name", remoteAgent.Name)
continue
}
remoteTool, sessionID, err := tools.NewKAgentRemoteA2ATool(remoteAgent.Name, remoteAgent.Description, remoteAgent.Url, nil, remoteAgent.Headers, propagateToken)
remoteTool, err := tools.NewKAgentRemoteA2ATool(remoteAgent.Name, remoteAgent.Description, remoteAgent.Url, nil, remoteAgent.Headers, propagateToken, remoteAgent.IsolateSessions)
if err != nil {
return nil, nil, fmt.Errorf("failed to create remote A2A tool for %s: %w", remoteAgent.Name, err)
}
if sessionID != "" {
subagentSessionIDs[remoteAgent.Name] = sessionID
return nil, fmt.Errorf("failed to create remote A2A tool for %s: %w", remoteAgent.Name, err)
}
remoteAgentTools = append(remoteAgentTools, remoteTool)
log.Info("Wired remote A2A agent tool", "name", remoteAgent.Name, "url", remoteAgent.Url)
}

localTools, err := buildAgentTools(agentConfig, remoteAgentTools, extraTools, log)
if err != nil {
return nil, nil, err
return nil, err
}

if agentConfig.Model == nil {
return nil, nil, fmt.Errorf("model configuration is required")
return nil, fmt.Errorf("model configuration is required")
}

llmModel, err := CreateLLM(ctx, agentConfig.Model, log)
if err != nil {
return nil, nil, fmt.Errorf("failed to create LLM: %w", err)
return nil, fmt.Errorf("failed to create LLM: %w", err)
}

if agentName == "" {
Expand Down Expand Up @@ -153,14 +141,14 @@ func CreateGoogleADKAgentWithSubagentSessionIDs(ctx context.Context, agentConfig

llmAgent, err := llmagent.New(llmAgentConfig)
if err != nil {
return nil, nil, fmt.Errorf("failed to create LLM agent: %w", err)
return nil, fmt.Errorf("failed to create LLM agent: %w", err)
}

log.Info("Successfully created Google ADK LLM agent",
"toolsCount", len(llmAgentConfig.Tools),
"toolsetsCount", len(llmAgentConfig.Toolsets))

return llmAgent, subagentSessionIDs, nil
return llmAgent, nil
}

func buildAgentTools(agentConfig *adk.AgentConfig, remoteAgentTools, extraTools []tool.Tool, log logr.Logger) ([]tool.Tool, error) {
Expand Down
2 changes: 1 addition & 1 deletion go/adk/pkg/agent/createllm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func runAgent(t *testing.T, agentCfg *adk.AgentConfig, prompt string) string {
t.Helper()
ctx := logr.NewContext(t.Context(), logr.Discard())

adkAgent, err := CreateGoogleADKAgent(ctx, agentCfg, "test-agent")
adkAgent, err := CreateGoogleADKAgent(ctx, agentCfg, "test-agent", nil)
require.NoError(t, err)

sessionService := adksession.InMemoryService()
Expand Down
20 changes: 10 additions & 10 deletions go/adk/pkg/runner/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,43 +37,43 @@ func CreateRunnerConfig(
memoryService *kagentmemory.KagentMemoryService,
kagentURL string,
httpClient *http.Client,
) (runner.Config, map[string]string, error) {
) (runner.Config, error) {
log := logr.FromContextOrDiscard(ctx)

var extraTools []adktool.Tool
if memoryService != nil {
saveTool, err := kagentmemory.NewSaveMemoryTool(memoryService)
if err != nil {
return runner.Config{}, nil, fmt.Errorf("failed to create save_memory tool: %w", err)
return runner.Config{}, fmt.Errorf("failed to create save_memory tool: %w", err)
}
extraTools = append(extraTools, saveTool)
}

if agentConfig.ShareTools != nil && *agentConfig.ShareTools && kagentURL != "" && httpClient != nil {
createTool, err := tools.NewCreateShareLinkTool(httpClient, kagentURL, appName)
if err != nil {
return runner.Config{}, nil, fmt.Errorf("failed to create create_share_link tool: %w", err)
return runner.Config{}, fmt.Errorf("failed to create create_share_link tool: %w", err)
}
listTool, err := tools.NewListShareLinksTool(httpClient, kagentURL, appName)
if err != nil {
return runner.Config{}, nil, fmt.Errorf("failed to create list_share_links tool: %w", err)
return runner.Config{}, fmt.Errorf("failed to create list_share_links tool: %w", err)
}
deleteTool, err := tools.NewDeleteShareLinkTool(httpClient, kagentURL, appName)
if err != nil {
return runner.Config{}, nil, fmt.Errorf("failed to create delete_share_link tool: %w", err)
return runner.Config{}, fmt.Errorf("failed to create delete_share_link tool: %w", err)
}
extraTools = append(extraTools, createTool, listTool, deleteTool)
log.Info("Share link tools enabled")
}

stsPlugin, err := buildTokenPropagationPlugin(ctx, log)
if err != nil {
return runner.Config{}, nil, err
return runner.Config{}, err
}

adkAgent, subagentSessionIDs, err := agent.CreateGoogleADKAgentWithSubagentSessionIDs(ctx, agentConfig, agentNameFromAppName(appName), stsPlugin, extraTools...)
adkAgent, err := agent.CreateGoogleADKAgent(ctx, agentConfig, agentNameFromAppName(appName), stsPlugin, extraTools...)
if err != nil {
return runner.Config{}, nil, fmt.Errorf("failed to create agent: %w", err)
return runner.Config{}, fmt.Errorf("failed to create agent: %w", err)
}

adkSessionService := sessionService
Expand All @@ -94,7 +94,7 @@ func CreateRunnerConfig(
if stsPlugin != nil {
p, err := stsPlugin.ADKPlugin()
if err != nil {
return runner.Config{}, nil, fmt.Errorf("failed to create STS ADK plugin: %w", err)
return runner.Config{}, fmt.Errorf("failed to create STS ADK plugin: %w", err)
}
if p != nil {
adkPlugins = append(adkPlugins, p)
Expand All @@ -111,7 +111,7 @@ func CreateRunnerConfig(
},
}

return cfg, subagentSessionIDs, nil
return cfg, nil
}

func buildTokenPropagationPlugin(ctx context.Context, log logr.Logger) (*sts.TokenPropagationPlugin, error) {
Expand Down
Loading
Loading