diff --git a/.golangci.yml b/.golangci.yml index c57e3f4d9..0e274c312 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -182,11 +182,6 @@ linters: linters: - staticcheck text: "SA1019:.*FunctionCall.*deprecated" - # adka2a/v2 migration requires a full a2a-go v2 upgrade (separate PR) - - path: pkg/a2a/ - linters: - - staticcheck - text: "SA1019:.*adka2a.*deprecated" # MCP sampling (SEP-2577) is deprecated upstream but functional during # the deprecation window; migrating off it is a separate effort - path: ^(e2e/sampling_test|pkg/runtime/sampling(_test)?|pkg/tools/sampling|pkg/tools/codemode/codemode_test|pkg/tools/mcp/(mcp|session_client)(_test)?)\.go$ diff --git a/e2e/a2a_test.go b/e2e/a2a_test.go index 759db2f5b..890658cda 100644 --- a/e2e/a2a_test.go +++ b/e2e/a2a_test.go @@ -11,8 +11,8 @@ import ( "testing" "time" - "github.com/a2aproject/a2a-go/a2a" - "github.com/a2aproject/a2a-go/a2asrv" + "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/a2aproject/a2a-go/v2/a2asrv" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,15 +27,8 @@ type a2aResponse struct { ID string `json:"id"` Error any `json:"error,omitempty"` - // Result holds the task with its artifacts. - Result *struct { - Artifacts []struct { - Parts []struct { - Kind string `json:"kind"` - Text string `json:"text"` - } `json:"parts"` - } `json:"artifacts"` - } `json:"result,omitempty"` + // Result holds the produced event, e.g. the task with its artifacts. + Result *a2a.StreamResponse `json:"result,omitempty"` } // textParts returns all text parts across every artifact in the response. @@ -43,11 +36,15 @@ func (r *a2aResponse) textParts() []string { if r.Result == nil { return nil } + task, ok := r.Result.Event.(*a2a.Task) + if !ok { + return nil + } var texts []string - for _, a := range r.Result.Artifacts { + for _, a := range task.Artifacts { for _, p := range a.Parts { - if p.Kind == "text" { - texts = append(texts, p.Text) + if _, ok := p.Content.(a2a.Text); ok { + texts = append(texts, p.Text()) } } } @@ -62,8 +59,9 @@ func TestA2AServer_AgentCard(t *testing.T) { assert.Equal(t, "basic", agentCard.Name) assert.NotEmpty(t, agentCard.Description) - assert.Equal(t, a2a.TransportProtocolJSONRPC, agentCard.PreferredTransport) - assert.Contains(t, agentCard.URL, "/invoke") + require.Len(t, agentCard.SupportedInterfaces, 1) + assert.Equal(t, a2a.TransportProtocolJSONRPC, agentCard.SupportedInterfaces[0].ProtocolBinding) + assert.Contains(t, agentCard.SupportedInterfaces[0].URL, "/invoke") assert.True(t, agentCard.Capabilities.Streaming) assert.NotEmpty(t, agentCard.Version) } @@ -74,7 +72,7 @@ func TestA2AServer_Invoke(t *testing.T) { _, runConfig := startRecordingAIProxy(t) agentCard := startA2AServer(t, "testdata/basic.yaml", runConfig) - resp := sendA2AMessage(t, agentCard.URL, "test-request-1", "msg-1", "What is 2+2? Answer with just the number.") + resp := sendA2AMessage(t, invokeURL(t, agentCard), "test-request-1", "msg-1", "What is 2+2? Answer with just the number.") assert.Equal(t, "2.0", resp.Jsonrpc) assert.Equal(t, "test-request-1", resp.ID) @@ -104,7 +102,7 @@ func TestA2AServer_MultipleRequests(t *testing.T) { requestID := fmt.Sprintf("test-request-%d", i) msgID := fmt.Sprintf("msg-%d", i) - resp := sendA2AMessage(t, agentCard.URL, requestID, msgID, message) + resp := sendA2AMessage(t, invokeURL(t, agentCard), requestID, msgID, message) assert.Equal(t, requestID, resp.ID) assert.Nil(t, resp.Error) @@ -119,7 +117,7 @@ func TestA2AServer_MultiAgent(t *testing.T) { _, runConfig := startRecordingAIProxy(t) agentCard := startA2AServer(t, "testdata/multi.yaml", runConfig) - resp := sendA2AMessage(t, agentCard.URL, "test-multi-1", "msg-multi-1", "Say hello.") + resp := sendA2AMessage(t, invokeURL(t, agentCard), "test-multi-1", "msg-multi-1", "Say hello.") assert.Equal(t, "test-multi-1", resp.ID) assert.Nil(t, resp.Error) @@ -130,21 +128,25 @@ func TestA2AServer_MultiAgent(t *testing.T) { assert.Contains(t, texts[len(texts)-1], "Hello") } -// sendA2AMessage sends a message/send JSON-RPC request and returns the parsed response. +// invokeURL returns the JSON-RPC endpoint advertised by the agent card. +func invokeURL(t *testing.T, card a2a.AgentCard) string { + t.Helper() + + require.NotEmpty(t, card.SupportedInterfaces) + return card.SupportedInterfaces[0].URL +} + +// sendA2AMessage sends a SendMessage JSON-RPC request and returns the parsed response. func sendA2AMessage(t *testing.T, url, requestID, messageID, text string) a2aResponse { t.Helper() + message := a2a.NewMessage(a2a.MessageRoleUser, a2a.NewTextPart(text)) + message.ID = messageID body, err := json.Marshal(map[string]any{ "jsonrpc": "2.0", "id": requestID, - "method": "message/send", - "params": map[string]any{ - "message": map[string]any{ - "messageId": messageID, - "role": "user", - "parts": []map[string]any{{"kind": "text", "text": text}}, - }, - }, + "method": "SendMessage", + "params": &a2a.SendMessageRequest{Message: message}, }) require.NoError(t, err) diff --git a/go.mod b/go.mod index 1fa72dc11..3ad66dc16 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/Masterminds/semver/v3 v3.5.0 github.com/Microsoft/go-winio v0.6.2 github.com/a2aproject/a2a-go v0.3.15 + github.com/a2aproject/a2a-go/v2 v2.4.0 github.com/alecthomas/chroma/v2 v2.27.0 github.com/alpkeskin/gotoon v0.1.1 github.com/anthropics/anthropic-sdk-go v1.66.0 @@ -104,7 +105,6 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/JohannesKaufmann/dom v0.3.1 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect - github.com/a2aproject/a2a-go/v2 v2.4.0 // indirect github.com/agext/levenshtein v1.2.1 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/apparentlymart/go-textseg/v17 v17.0.1 // indirect diff --git a/pkg/a2a/executor_wrapper.go b/pkg/a2a/executor_wrapper.go index 3b4d83f06..45e42285a 100644 --- a/pkg/a2a/executor_wrapper.go +++ b/pkg/a2a/executor_wrapper.go @@ -2,11 +2,11 @@ package a2a import ( "context" + "iter" - "github.com/a2aproject/a2a-go/a2a" - "github.com/a2aproject/a2a-go/a2asrv" - "github.com/a2aproject/a2a-go/a2asrv/eventqueue" - "google.golang.org/adk/v2/server/adka2a" + "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/a2aproject/a2a-go/v2/a2asrv" + adka2a "google.golang.org/adk/v2/server/adka2a/v2" ) // executorWrapper wraps an ADK executor and fixes artifact update events @@ -15,48 +15,45 @@ type executorWrapper struct { executor *adka2a.Executor } +var ( + _ a2asrv.AgentExecutor = (*executorWrapper)(nil) + _ a2asrv.AgentExecutionCleaner = (*executorWrapper)(nil) +) + func newExecutorWrapper(config adka2a.ExecutorConfig) *executorWrapper { return &executorWrapper{ executor: adka2a.NewExecutor(config), } } -func (w *executorWrapper) Execute(ctx context.Context, reqCtx *a2asrv.RequestContext, queue eventqueue.Queue) error { - // Create a wrapping queue that fixes events before sending them - fixedQueue := &fixingQueue{ - queue: queue, - } - return w.executor.Execute(ctx, reqCtx, fixedQueue) +func (w *executorWrapper) Execute(ctx context.Context, execCtx *a2asrv.ExecutorContext) iter.Seq2[a2a.Event, error] { + return fixArtifactEvents(w.executor.Execute(ctx, execCtx)) } -func (w *executorWrapper) Cancel(ctx context.Context, reqCtx *a2asrv.RequestContext, queue eventqueue.Queue) error { - return w.executor.Cancel(ctx, reqCtx, queue) +func (w *executorWrapper) Cancel(ctx context.Context, execCtx *a2asrv.ExecutorContext) iter.Seq2[a2a.Event, error] { + return w.executor.Cancel(ctx, execCtx) } -// fixingQueue wraps an eventqueue.Queue and fixes artifact update events -type fixingQueue struct { - queue eventqueue.Queue +// Cleanup delegates to the ADK executor, which implements +// a2asrv.AgentExecutionCleaner; dropping it would change cleanup semantics. +func (w *executorWrapper) Cleanup(ctx context.Context, execCtx *a2asrv.ExecutorContext, result a2a.SendMessageResult, err error) { + w.executor.Cleanup(ctx, execCtx, result, err) } -func (fq *fixingQueue) Write(ctx context.Context, event a2a.Event) error { - // Fix artifact update events with nil Parts - if artifactEvent, ok := event.(*a2a.TaskArtifactUpdateEvent); ok { - if artifactEvent.Artifact != nil && artifactEvent.Artifact.Parts == nil { - // Replace nil with an empty slice - artifactEvent.Artifact.Parts = []a2a.Part{} +// fixArtifactEvents wraps an event sequence and fixes artifact update events +// with nil Parts before yielding them. Everything else passes through unchanged. +func fixArtifactEvents(events iter.Seq2[a2a.Event, error]) iter.Seq2[a2a.Event, error] { + return func(yield func(a2a.Event, error) bool) { + for event, err := range events { + if artifactEvent, ok := event.(*a2a.TaskArtifactUpdateEvent); ok { + if artifactEvent.Artifact != nil && artifactEvent.Artifact.Parts == nil { + // Replace nil with an empty slice + artifactEvent.Artifact.Parts = a2a.ContentParts{} + } + } + if !yield(event, err) { + return + } } } - return fq.queue.Write(ctx, event) -} - -func (fq *fixingQueue) Read(ctx context.Context) (a2a.Event, a2a.TaskVersion, error) { - return fq.queue.Read(ctx) -} - -func (fq *fixingQueue) WriteVersioned(ctx context.Context, event a2a.Event, version a2a.TaskVersion) error { - return fq.queue.WriteVersioned(ctx, event, version) -} - -func (fq *fixingQueue) Close() error { - return fq.queue.Close() } diff --git a/pkg/a2a/executor_wrapper_test.go b/pkg/a2a/executor_wrapper_test.go index 0ba829a6b..52ba0603a 100644 --- a/pkg/a2a/executor_wrapper_test.go +++ b/pkg/a2a/executor_wrapper_test.go @@ -1,45 +1,43 @@ package a2a import ( - "context" "encoding/json" + "errors" + "iter" "testing" - "github.com/a2aproject/a2a-go/a2a" - "github.com/a2aproject/a2a-go/a2asrv" + "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/a2aproject/a2a-go/v2/a2asrv" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + adka2a "google.golang.org/adk/v2/server/adka2a/v2" ) -type mockQueue struct { - events []a2a.Event -} - -func (m *mockQueue) Write(_ context.Context, event a2a.Event) error { - m.events = append(m.events, event) - return nil -} - -func (m *mockQueue) Read(context.Context) (a2a.Event, a2a.TaskVersion, error) { - return nil, a2a.TaskVersionMissing, nil -} - -func (m *mockQueue) WriteVersioned(_ context.Context, event a2a.Event, _ a2a.TaskVersion) error { - m.events = append(m.events, event) - return nil +func sourceOf(events ...a2a.Event) iter.Seq2[a2a.Event, error] { + return func(yield func(a2a.Event, error) bool) { + for _, event := range events { + if !yield(event, nil) { + return + } + } + } } -func (m *mockQueue) Close() error { - return nil +func collect(t *testing.T, events iter.Seq2[a2a.Event, error]) []a2a.Event { + t.Helper() + var collected []a2a.Event + for event, err := range events { + require.NoError(t, err) + collected = append(collected, event) + } + return collected } -func TestFixingQueue_NilParts(t *testing.T) { +func TestFixArtifactEvents_NilParts(t *testing.T) { t.Parallel() - mock := &mockQueue{} - fq := &fixingQueue{queue: mock} // Create an artifact update event with nil Parts - err := fq.Write(t.Context(), &a2a.TaskArtifactUpdateEvent{ + source := sourceOf(&a2a.TaskArtifactUpdateEvent{ ContextID: "test-context", TaskID: "test-task", Append: true, @@ -48,66 +46,115 @@ func TestFixingQueue_NilParts(t *testing.T) { Parts: nil, }, }) - require.NoError(t, err) - require.Len(t, mock.events, 1) - written := mock.events[0].(*a2a.TaskArtifactUpdateEvent) - assert.NotNil(t, written.Artifact.Parts) - assert.Empty(t, written.Artifact.Parts) + events := collect(t, fixArtifactEvents(source)) + require.Len(t, events, 1) + + fixed := events[0].(*a2a.TaskArtifactUpdateEvent) + assert.NotNil(t, fixed.Artifact.Parts) + assert.Empty(t, fixed.Artifact.Parts) // Verify it serializes correctly - data, err := json.Marshal(written.Artifact) + data, err := json.Marshal(fixed.Artifact) require.NoError(t, err) assert.JSONEq(t, `{"artifactId":"test-artifact","parts":[]}`, string(data)) } -func TestFixingQueue_WithParts(t *testing.T) { +func TestFixArtifactEvents_WithParts(t *testing.T) { t.Parallel() - mock := &mockQueue{} - fq := &fixingQueue{queue: mock} // Create an artifact update event with actual parts - event := &a2a.TaskArtifactUpdateEvent{ + source := sourceOf(&a2a.TaskArtifactUpdateEvent{ ContextID: "test-context", TaskID: "test-task", Append: true, Artifact: &a2a.Artifact{ - ID: "test-artifact", - Parts: []a2a.Part{ - a2a.TextPart{Text: "Hello"}, - }, + ID: "test-artifact", + Parts: a2a.ContentParts{a2a.NewTextPart("Hello")}, }, + }) + + events := collect(t, fixArtifactEvents(source)) + require.Len(t, events, 1) + + // Verify the event was yielded unchanged + fixed := events[0].(*a2a.TaskArtifactUpdateEvent) + require.Len(t, fixed.Artifact.Parts, 1) + assert.Equal(t, "Hello", fixed.Artifact.Parts[0].Text()) +} + +func TestFixArtifactEvents_NonArtifactEvent(t *testing.T) { + t.Parallel() + + // Create a different type of event + execCtx := &a2asrv.ExecutorContext{ + TaskID: "test-task", + ContextID: "test-context", } + event := a2a.NewStatusUpdateEvent(execCtx, a2a.TaskStateCompleted, nil) - // Write the event through the fixing queue - err := fq.Write(t.Context(), event) - require.NoError(t, err) + events := collect(t, fixArtifactEvents(sourceOf(event))) + + // Verify the event was yielded unchanged + require.Len(t, events, 1) + assert.Same(t, event, events[0]) +} + +func TestFixArtifactEvents_ErrorPassthrough(t *testing.T) { + t.Parallel() - // Verify the event was written unchanged - require.Len(t, mock.events, 1) - written := mock.events[0].(*a2a.TaskArtifactUpdateEvent) + wantErr := errors.New("agent run failed") + source := func(yield func(a2a.Event, error) bool) { + yield(nil, wantErr) + } - assert.Len(t, written.Artifact.Parts, 1) + var errs []error + for event, err := range fixArtifactEvents(source) { + assert.Nil(t, event) + errs = append(errs, err) + } + require.Len(t, errs, 1) + require.ErrorIs(t, errs[0], wantErr) } -func TestFixingQueue_NonArtifactEvent(t *testing.T) { +func TestFixArtifactEvents_StopsSourceWhenConsumerStops(t *testing.T) { t.Parallel() - mock := &mockQueue{} - fq := &fixingQueue{queue: mock} - // Create a different type of event - reqCtx := &a2asrv.RequestContext{ + execCtx := &a2asrv.ExecutorContext{ TaskID: "test-task", ContextID: "test-context", } - event := a2a.NewStatusUpdateEvent(reqCtx, a2a.TaskStateCompleted, nil) + yielded := 0 + source := func(yield func(a2a.Event, error) bool) { + for { + yielded++ + if !yield(a2a.NewStatusUpdateEvent(execCtx, a2a.TaskStateWorking, nil), nil) { + return + } + } + } - // Write the event through the fixing queue - err := fq.Write(t.Context(), event) - require.NoError(t, err) + for range fixArtifactEvents(source) { + break + } + + assert.Equal(t, 1, yielded) +} + +func TestExecutorWrapper_Cancel(t *testing.T) { + t.Parallel() + + wrapper := newExecutorWrapper(adka2a.ExecutorConfig{}) + execCtx := &a2asrv.ExecutorContext{ + TaskID: "test-task", + ContextID: "test-context", + } + + events := collect(t, wrapper.Cancel(t.Context(), execCtx)) + require.Len(t, events, 1) - // Verify the event was written unchanged - require.Len(t, mock.events, 1) - assert.IsType(t, &a2a.TaskStatusUpdateEvent{}, mock.events[0]) + statusEvent, ok := events[0].(*a2a.TaskStatusUpdateEvent) + require.True(t, ok) + assert.Equal(t, a2a.TaskStateCanceled, statusEvent.Status.State) } diff --git a/pkg/a2a/server.go b/pkg/a2a/server.go index 571ad0587..90fc855b4 100644 --- a/pkg/a2a/server.go +++ b/pkg/a2a/server.go @@ -10,8 +10,8 @@ import ( "path/filepath" "strings" - "github.com/a2aproject/a2a-go/a2a" - "github.com/a2aproject/a2a-go/a2asrv" + "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/a2aproject/a2a-go/v2/a2asrv" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" @@ -19,7 +19,7 @@ import ( // OTel Logs 0.21 support (google/adk-go#1335) is merged upstream but not yet // tagged. Replace with the first v2 tag that contains that commit. "google.golang.org/adk/v2/runner" - "google.golang.org/adk/v2/server/adka2a" + adka2a "google.golang.org/adk/v2/server/adka2a/v2" adksession "google.golang.org/adk/v2/session" "github.com/docker/docker-agent/pkg/config" @@ -131,6 +131,7 @@ func newServer(t *team.Team, agentFilename, agentName string, sessStore session. name := strings.TrimSuffix(filepath.Base(agentFilename), filepath.Ext(agentFilename)) agentPath := "/invoke" + invokeURL := baseURL.JoinPath(agentPath).String() agentCard := &a2a.AgentCard{ Name: name, Description: adkAgent.Description(), @@ -140,12 +141,11 @@ func newServer(t *team.Team, agentFilename, agentName string, sessStore session. Description: adkAgent.Description(), Tags: []string{"llm", "docker agent"}, }}, - PreferredTransport: a2a.TransportProtocolJSONRPC, - URL: baseURL.JoinPath(agentPath).String(), - Capabilities: a2a.AgentCapabilities{Streaming: true}, - Version: version.Version, - DefaultInputModes: []string{}, - DefaultOutputModes: []string{}, + SupportedInterfaces: []*a2a.AgentInterface{a2a.NewAgentInterface(invokeURL, a2a.TransportProtocolJSONRPC)}, + Capabilities: a2a.AgentCapabilities{Streaming: true}, + Version: version.Version, + DefaultInputModes: []string{}, + DefaultOutputModes: []string{}, } executor := newExecutorWrapper(adka2a.ExecutorConfig{ diff --git a/pkg/a2a/server_invoke_test.go b/pkg/a2a/server_invoke_test.go index 9e24e43a3..e88e4f18e 100644 --- a/pkg/a2a/server_invoke_test.go +++ b/pkg/a2a/server_invoke_test.go @@ -8,8 +8,8 @@ import ( "sync/atomic" "testing" - "github.com/a2aproject/a2a-go/a2a" - "github.com/a2aproject/a2a-go/a2aclient" + "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/a2aproject/a2a-go/v2/a2aclient" "github.com/stretchr/testify/require" dagent "github.com/docker/docker-agent/pkg/agent" @@ -97,13 +97,12 @@ func TestServer_ToolPolicyOverInvoke(t *testing.T) { root := dagent.New("root", "You are a test agent", dagent.WithModel(provider), dagent.WithTools(tool)) server := startInvokeServer(t, team.New(team.WithAgents(root)), session.NewInMemorySessionStore(), servesafety.Resolved{Policy: tc.policy}) - client, err := a2aclient.NewFromEndpoints(t.Context(), []a2a.AgentInterface{{ - Transport: a2a.TransportProtocolJSONRPC, - URL: fmt.Sprintf("http://%s/invoke", server.Addr()), - }}) + client, err := a2aclient.NewFromEndpoints(t.Context(), []*a2a.AgentInterface{ + a2a.NewAgentInterface(fmt.Sprintf("http://%s/invoke", server.Addr()), a2a.TransportProtocolJSONRPC), + }) require.NoError(t, err) - _, err = client.SendMessage(t.Context(), &a2a.MessageSendParams{ - Message: a2a.NewMessage(a2a.MessageRoleUser, a2a.TextPart{Text: "run the tool"}), + _, err = client.SendMessage(t.Context(), &a2a.SendMessageRequest{ + Message: a2a.NewMessage(a2a.MessageRoleUser, a2a.NewTextPart("run the tool")), }) require.NoError(t, err) require.Equal(t, tc.executed, executions.Load()) @@ -134,23 +133,22 @@ func TestServer_RejectsNonA2AContextCollision(t *testing.T) { require.NoError(t, store.AddSession(t.Context(), existing)) server := startInvokeServer(t, team.New(team.WithAgents(root)), store, servesafety.Resolved{Policy: session.SafetyPolicyAutonomous}) - client, err := a2aclient.NewFromEndpoints(t.Context(), []a2a.AgentInterface{{ - Transport: a2a.TransportProtocolJSONRPC, - URL: fmt.Sprintf("http://%s/invoke", server.Addr()), - }}) + client, err := a2aclient.NewFromEndpoints(t.Context(), []*a2a.AgentInterface{ + a2a.NewAgentInterface(fmt.Sprintf("http://%s/invoke", server.Addr()), a2a.TransportProtocolJSONRPC), + }) require.NoError(t, err) - message := a2a.NewMessage(a2a.MessageRoleUser, a2a.TextPart{Text: "run the tool"}) + message := a2a.NewMessage(a2a.MessageRoleUser, a2a.NewTextPart("run the tool")) message.ContextID = "colliding-context" - got, err := client.SendMessage(t.Context(), &a2a.MessageSendParams{Message: message}) + got, err := client.SendMessage(t.Context(), &a2a.SendMessageRequest{Message: message}) require.NoError(t, err) task, ok := got.(*a2a.Task) require.True(t, ok) require.Equal(t, a2a.TaskStateFailed, task.Status.State) require.NotNil(t, task.Status.Message) require.Len(t, task.Status.Message.Parts, 1) - failure, ok := task.Status.Message.Parts[0].(a2a.TextPart) - require.True(t, ok) - require.Equal(t, "agent run failed: context ID is not available", failure.Text) + failure := task.Status.Message.Parts[0] + require.IsType(t, a2a.Text(""), failure.Content) + require.Equal(t, "agent run failed: context ID is not available", failure.Text()) require.Zero(t, executions.Load()) stored, err := store.GetSession(t.Context(), existing.ID) diff --git a/pkg/a2a/server_test.go b/pkg/a2a/server_test.go index 6aa0b3622..1b34c0afc 100644 --- a/pkg/a2a/server_test.go +++ b/pkg/a2a/server_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/a2aproject/a2a-go/a2asrv" + "github.com/a2aproject/a2a-go/v2/a2asrv" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require"