Repository: google/adk-go
File: server/adka2a/executor.go (Go)

Code before:

import (
	"context"
	"fmt"

	"github.com/a2aproject/a2a-go/a2a"
	"github.com/a2aproject/a2a-go/a2asrv"
	"github.com/a2aproject/a2a-go/a2asrv/eventqueue"

	"google.golang.org/genai"
	"google.golang.org/adk/agent"
	"google.golang.org/adk/runner"
	"google.golang.org/adk/session"
)

// BeforeExecuteCallback is the callback which will be called before an execution is started.
type BeforeExecuteCallback func(ctx context.Context, reqCtx *a2asrv.RequestContext) (context.Context, error)

// AfterEventCallback is the callback which will be called after an ADK event is converted to an A2A event.
type AfterEventCallback func(ctx ExecutorContext, event *session.Event, processed *a2a.TaskArtifactUpdateEvent) error

// AfterExecuteCallback is the callback which will be called after an execution resolved into a completed or failed task.
type AfterExecuteCallback func(ctx ExecutorContext, finalEvent *a2a.TaskStatusUpdateEvent, err error) error

// A2AToGenAIPartConverter is a custom converter for converting A2A parts to GenAI parts.
// nil returns are ignored and the default conversion is used.
type A2AToGenAIPartConverter func(part a2a.Part) (*genai.Part, error)

// GenAIToA2APartConverter is a custom converter for converting GenAI parts to A2A parts.
// nil returns are ignored and the default conversion is used.
type GenAIToA2APartConverter func(part *genai.Part) (a2a.Part, error)

// ExecutorConfig allows to configure Executor.
type ExecutorConfig struct {
	// RunnerConfig is the configuration which will be used for [runner.New] during A2A Execute invocation.
	RunnerConfig runner.Config

	// RunConfig is the configuration which will be passed to [runner.Runner.Run] during A2A Execute invocation.
	RunConfig agent.RunConfig

	// BeforeExecuteCallback is the callback which will be called before an execution is started.
	// It can be used to instrument a context or prevent the execution by returning an error.
	BeforeExecuteCallback BeforeExecuteCallback

	// AfterEventCallback is the callback which will be called after an ADK event is successfully converted to an A2A event.
	// This gives an opportunity to enrich the event with additional metadata or abort the execution by returning an error.
	// The callback is not invoked for errors originating from ADK or event processing. Such errors are converted to
	// TaskStatusUpdateEvent-s with TaskStateFailed state. If needed these can be intercepted using AfterExecuteCallback.
	AfterEventCallback AfterEventCallback

	// AfterExecuteCallback is the callback which will be called after an execution resolved into a completed or failed task.

Review comment (suggestion):
> nil returns are ignored and the default conversion is used

Not sure about this part (pun intended).
It would be nice to allow fully ignoring parts on this level (and if all parts are ignored event is not emitted).

I think the cleanest approach would be making nil serve as "skip part" marker.
Default conversion should remain accessible using `adka2a.To(A2A|Genai)Part`:
```go
converter := func(part a2a.Part) (*genai.Part, error) {
  switch {
    case shouldSkip(part):
      return nil, nil
    case shouldCustomConvert(part):
      return customConvert(part)
    default:
      return adka2a.ToGenaiPart(part)
  } 
}
```

Another alternative is a special `adka2a.ErrSkipPart`, but I think it looks dirtier / more obscure.
```go
converter := func(part a2a.Part) (*genai.Part, error) {
  switch {
    case shouldSkip(part):
      return nil, adka2a.ErrSkipPart
    case shouldCustomConvert(part):
      return customConvert(part)
    default:
      return nil, nil
  } 
}
```


Code after:

import (
	"context"
	"fmt"

	"github.com/a2aproject/a2a-go/a2a"
	"github.com/a2aproject/a2a-go/a2asrv"
	"github.com/a2aproject/a2a-go/a2asrv/eventqueue"

	"google.golang.org/genai"

	"google.golang.org/adk/agent"
	"google.golang.org/adk/runner"
	"google.golang.org/adk/session"
)

// BeforeExecuteCallback is the callback which will be called before an execution is started.
type BeforeExecuteCallback func(ctx context.Context, reqCtx *a2asrv.RequestContext) (context.Context, error)

// AfterEventCallback is the callback which will be called after an ADK event is converted to an A2A event.
type AfterEventCallback func(ctx ExecutorContext, event *session.Event, processed *a2a.TaskArtifactUpdateEvent) error

// AfterExecuteCallback is the callback which will be called after an execution resolved into a completed or failed task.
type AfterExecuteCallback func(ctx ExecutorContext, finalEvent *a2a.TaskStatusUpdateEvent, err error) error

// A2APartConverter is a custom converter for converting A2A parts to GenAI parts.
// Implementations should generally remember to leverage adka2a.ToGenAiPart for default conversions
// nil returns are considered intentionally dropped parts.
type A2APartConverter func(ctx context.Context, a2aEvent a2a.Event, part a2a.Part) (*genai.Part, error)

// GenAIPartConverter is a custom converter for converting GenAI parts to A2A parts.
// Implementations should generally remember to leverage adka2a.ToA2APart for default conversions
// nil returns are considered intentionally dropped parts.
type GenAIPartConverter func(ctx context.Context, adkEvent *session.Event, part *genai.Part) (a2a.Part, error)

// ExecutorConfig allows to configure Executor.
type ExecutorConfig struct {
	// RunnerConfig is the configuration which will be used for [runner.New] during A2A Execute invocation.
	RunnerConfig runner.Config

	// RunConfig is the configuration which will be passed to [runner.Runner.Run] during A2A Execute invocation.
	RunConfig agent.RunConfig

	// BeforeExecuteCallback is the callback which will be called before an execution is started.
	// It can be used to instrument a context or prevent the execution by returning an error.
	BeforeExecuteCallback BeforeExecuteCallback

	// AfterEventCallback is the callback which will be called after an ADK event is successfully converted to an A2A event.
	// This gives an opportunity to enrich the event with additional metadata or abort the execution by returning an error.
	// The callback is not invoked for errors originating from ADK or event processing. Such errors are converted to
	// TaskStatusUpdateEvent-s with TaskStateFailed state. If needed these can be intercepted using AfterExecuteCallback.