Skip to content
Draft
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
8 changes: 5 additions & 3 deletions go/adk/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"strings"
"time"

a2atype "github.com/a2aproject/a2a-go/a2a"
a2atype "github.com/a2aproject/a2a-go/v2/a2a"
"github.com/go-logr/logr"
"github.com/go-logr/zapr"
"github.com/kagent-dev/kagent/go/adk/pkg/a2a"
Expand Down Expand Up @@ -195,11 +195,13 @@ func main() {
Name: "go-adk-agent",
Description: "Go-based Agent Development Kit",
Version: "0.2.0",
SupportedInterfaces: []*a2atype.AgentInterface{
a2atype.NewAgentInterface("/", a2atype.TransportProtocolJSONRPC),
},
}
}
agentCard.Capabilities = a2atype.AgentCapabilities{
Streaming: stream,
StateTransitionHistory: true,
Streaming: stream,
}

// Delegate server, task store, and remaining infrastructure to app.New.
Expand Down
11 changes: 6 additions & 5 deletions go/adk/examples/byo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import (
"log"
"os"

a2atype "github.com/a2aproject/a2a-go/a2a"
a2atype "github.com/a2aproject/a2a-go/v2/a2a"
"github.com/go-logr/zapr"
"github.com/kagent-dev/kagent/go/adk/pkg/app"
"github.com/kagent-dev/kagent/go/adk/pkg/models"
Expand All @@ -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/v2"
adksession "google.golang.org/adk/session"
)

Expand Down Expand Up @@ -127,10 +127,11 @@ func main() {
Name: "byo-parallel-agent",
Description: "A BYO agent that runs creative and technical writers in parallel",
Version: "1.0.0",
URL: "http://localhost:8082",
SupportedInterfaces: []*a2atype.AgentInterface{
a2atype.NewAgentInterface("http://localhost:8082", a2atype.TransportProtocolJSONRPC),
},
Capabilities: a2atype.AgentCapabilities{
Streaming: stream,
StateTransitionHistory: true,
Streaming: stream,
},
DefaultInputModes: []string{"text/plain"},
DefaultOutputModes: []string{"text/plain"},
Expand Down
12 changes: 7 additions & 5 deletions go/adk/pkg/a2a/agentcard.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package a2a

import (
a2atype "github.com/a2aproject/a2a-go/a2a"
a2atype "github.com/a2aproject/a2a-go/v2/a2a"
adkagent "google.golang.org/adk/agent"
"google.golang.org/adk/server/adka2a"
"google.golang.org/adk/server/adka2a/v2"
)

// EnrichAgentCard populates the agent card with skills derived from the ADK
Expand All @@ -22,8 +22,10 @@ func EnrichAgentCard(card *a2atype.AgentCard, agent adkagent.Agent) {
card.Description = agent.Description()
}

// Default to JSONRPC when no transport is explicitly configured.
if card.PreferredTransport == "" {
card.PreferredTransport = a2atype.TransportProtocolJSONRPC
// Default to JSONRPC when no interface is explicitly configured.
if len(card.SupportedInterfaces) == 0 {
card.SupportedInterfaces = []*a2atype.AgentInterface{
a2atype.NewAgentInterface("/", a2atype.TransportProtocolJSONRPC),
}
}
}
2 changes: 1 addition & 1 deletion go/adk/pkg/a2a/consts.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package a2a

import "google.golang.org/adk/server/adka2a"
import "google.golang.org/adk/server/adka2a/v2"

const (
StateKeySessionName = "session_name"
Expand Down
69 changes: 33 additions & 36 deletions go/adk/pkg/a2a/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,25 @@ import (
"encoding/json"
"maps"

a2atype "github.com/a2aproject/a2a-go/a2a"
"google.golang.org/adk/server/adka2a"
a2atype "github.com/a2aproject/a2a-go/v2/a2a"
"google.golang.org/adk/server/adka2a/v2"
adksession "google.golang.org/adk/session"
"google.golang.org/genai"
)

// isEmptyDataPart returns true if the part is a DataPart with nil or empty Data.
// The ADK processor emits such parts as cleanup signals for streaming partial
// artifacts and as a fallback for unrecognized GenAI part types.
func isEmptyDataPart(part a2atype.Part) bool {
dp, ok := part.(a2atype.DataPart)
return ok && len(dp.Data) == 0
func isEmptyDataPart(part *a2atype.Part) bool {
dp := asDataPart(part)
return dp != nil && len(dp) == 0
}

// filterTextParts returns only TextParts from the given parts.
func filterTextParts(parts a2atype.ContentParts) a2atype.ContentParts {
var out a2atype.ContentParts
for _, p := range parts {
if _, ok := p.(a2atype.TextPart); ok {
if p != nil && p.Text() != "" {
out = append(out, p)
}
}
Expand Down Expand Up @@ -56,23 +56,23 @@ func messageToGenAIContent(ctx context.Context, msg *a2atype.Message) (*genai.Co
}

// a2aPartConverter converts inbound A2A parts to GenAI parts.
func a2aPartConverter(_ context.Context, _ a2atype.Event, part a2atype.Part) (*genai.Part, error) {
func a2aPartConverter(_ context.Context, _ a2atype.Event, part *a2atype.Part) (*genai.Part, error) {
dp := asDataPart(part)
if dp == nil {
// Text and file parts: delegate to ADK default.
return adka2a.ToGenAIPart(part)
}

// DataPart with kagent_type metadata: convert explicitly.
if dp.Metadata != nil {
if _, has := dp.Metadata[GetKAgentMetadataKey(A2ADataPartMetadataTypeKey)]; has {
return convertDataPartToGenAI(dp, GetKAgentMetadataKey(A2ADataPartMetadataTypeKey))
if part != nil && part.Metadata != nil {
if _, has := part.Metadata[GetKAgentMetadataKey(A2ADataPartMetadataTypeKey)]; has {
return convertDataPartToGenAI(dp, part.Metadata, GetKAgentMetadataKey(A2ADataPartMetadataTypeKey))
}
}

// DataPart with adk_type metadata (produced by the ADK itself): delegate.
if dp.Metadata != nil {
if _, has := dp.Metadata[adka2a.ToA2AMetaKey(A2ADataPartMetadataTypeKey)]; has {
if part != nil && part.Metadata != nil {
if _, has := part.Metadata[adka2a.ToA2AMetaKey(A2ADataPartMetadataTypeKey)]; has {
return adka2a.ToGenAIPart(part)
}
}
Expand All @@ -84,66 +84,63 @@ func a2aPartConverter(_ context.Context, _ a2atype.Event, part a2atype.Part) (*g

// convertDataPartToGenAI converts a DataPart with a type metadata key
// (either adk_type or kagent_type) back to GenAI for inbound message processing.
func convertDataPartToGenAI(p *a2atype.DataPart, typeKey string) (*genai.Part, error) {
if p == nil {
func convertDataPartToGenAI(data map[string]any, metadata map[string]any, typeKey string) (*genai.Part, error) {
if data == nil {
return nil, nil
}
partType, _ := p.Metadata[typeKey].(string)
partType, _ := metadata[typeKey].(string)
switch partType {
case A2ADataPartMetadataTypeFunctionCall:
name, _ := p.Data[PartKeyName].(string)
funcArgs, _ := p.Data[PartKeyArgs].(map[string]any)
name, _ := data[PartKeyName].(string)
funcArgs, _ := data[PartKeyArgs].(map[string]any)
if name != "" {
genaiPart := genai.NewPartFromFunctionCall(name, funcArgs)
if id, ok := p.Data[PartKeyID].(string); ok && id != "" {
if id, ok := data[PartKeyID].(string); ok && id != "" {
genaiPart.FunctionCall.ID = id
}
return genaiPart, nil
}
case A2ADataPartMetadataTypeFunctionResponse:
name, _ := p.Data[PartKeyName].(string)
response, _ := p.Data[PartKeyResponse].(map[string]any)
name, _ := data[PartKeyName].(string)
response, _ := data[PartKeyResponse].(map[string]any)
if name != "" {
genaiPart := genai.NewPartFromFunctionResponse(name, response)
if id, ok := p.Data[PartKeyID].(string); ok && id != "" {
if id, ok := data[PartKeyID].(string); ok && id != "" {
genaiPart.FunctionResponse.ID = id
}
return genaiPart, nil
}
}
return adka2a.ToGenAIPart(p)
return adka2a.ToGenAIPart(a2atype.NewDataPart(data))
}

// 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 stampSubagentSessionID(part *a2atype.Part, subagentSessionIDs map[string]string) *a2atype.Part {
if part == nil {
return nil
}
stampSubagentSessionIDOnDataPart(part, subagentSessionIDs)
return part
}

func stampSubagentSessionIDOnDataPart(dp *a2atype.DataPart, subagentSessionIDs map[string]string) {
func stampSubagentSessionIDOnDataPart(dp *a2atype.Part, subagentSessionIDs map[string]string) {
if dp == nil || len(subagentSessionIDs) == 0 {
return
}
view := asDataPart(dp)
if view == nil {
return
}
if dp.Metadata == nil {
dp.Metadata = map[string]any{}
}
partType, _ := ReadMetadataValue(dp.Metadata, A2ADataPartMetadataTypeKey)
if partType != A2ADataPartMetadataTypeFunctionCall {
return
}
toolName, _ := dp.Data[PartKeyName].(string)
toolName, _ := view[PartKeyName].(string)
if toolName == "" {
return
}
Expand Down
Loading