From 5bd9b1b2003a8792843b6028ba324fb97808e9b2 Mon Sep 17 00:00:00 2001 From: Yaroslav Shevchuk Date: Fri, 28 Aug 2026 11:04:57 +0000 Subject: [PATCH] push config commands --- internal/cli/task.go | 1 + internal/cli/task_push_config.go | 34 +++ internal/cli/task_push_config_create.go | 99 +++++++ internal/cli/task_push_config_delete.go | 52 ++++ internal/cli/task_push_config_get.go | 56 ++++ internal/cli/task_push_config_list.go | 67 +++++ internal/cli/task_push_config_test.go | 348 ++++++++++++++++++++++++ internal/output/output.go | 57 ++++ 8 files changed, 714 insertions(+) create mode 100644 internal/cli/task_push_config.go create mode 100644 internal/cli/task_push_config_create.go create mode 100644 internal/cli/task_push_config_delete.go create mode 100644 internal/cli/task_push_config_get.go create mode 100644 internal/cli/task_push_config_list.go create mode 100644 internal/cli/task_push_config_test.go diff --git a/internal/cli/task.go b/internal/cli/task.go index 53d5b57..a02177f 100644 --- a/internal/cli/task.go +++ b/internal/cli/task.go @@ -28,6 +28,7 @@ func newTaskCmd(cfg *globalConfig) *cobra.Command { newTaskListCmd(cfg), newTaskCancelCmd(cfg), newTaskSubscribeCmd(cfg), + newTaskPushConfigCmd(cfg), ) return cmd } diff --git a/internal/cli/task_push_config.go b/internal/cli/task_push_config.go new file mode 100644 index 0000000..0752426 --- /dev/null +++ b/internal/cli/task_push_config.go @@ -0,0 +1,34 @@ +// Copyright 2026 The A2A Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cli + +import ( + "github.com/spf13/cobra" +) + +func newTaskPushConfigCmd(cfg *globalConfig) *cobra.Command { + cmd := &cobra.Command{ + Use: "push-config", + Aliases: []string{"push"}, + Short: "Manage task push-notification configurations", + } + cmd.AddCommand( + newPushConfigCreateCmd(cfg), + newPushConfigGetCmd(cfg), + newPushConfigListCmd(cfg), + newPushConfigDeleteCmd(cfg), + ) + return cmd +} diff --git a/internal/cli/task_push_config_create.go b/internal/cli/task_push_config_create.go new file mode 100644 index 0000000..b6a2bb8 --- /dev/null +++ b/internal/cli/task_push_config_create.go @@ -0,0 +1,99 @@ +// Copyright 2026 The A2A Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cli + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "github.com/a2aproject/a2a-go/v2/a2a" +) + +type pushConfigCreateFlags struct { + taskID string + tenant string + url string + id string + token string + authScheme string + authCredentials string +} + +func newPushConfigCreateCmd(cfg *globalConfig) *cobra.Command { + var f pushConfigCreateFlags + + cmd := &cobra.Command{ + Use: "create ", + Short: "Create a push-notification configuration for a task", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + f.taskID = args[0] + f.tenant = cfg.tenant + pc, err := buildPushConfig(f) + if err != nil { + return err + } + + ctx, cancel := context.WithTimeout(cmd.Context(), cfg.timeout) + defer cancel() + ctx = withServiceParams(ctx, cfg) + + client, err := newAgentClient(ctx, cfg) + if err != nil { + return fmt.Errorf("failed to create a client: %w", err) + } + defer destroyClient(cfg, client) + + result, err := client.CreateTaskPushConfig(ctx, pc) + if err != nil { + return fmt.Errorf("failed to create push config: %w", err) + } + if err := cfg.PrintPushConfig(result); err != nil { + return fmt.Errorf("failed to print push config: %w", err) + } + return nil + }, + } + + fl := cmd.Flags() + fl.StringVar(&f.url, "url", "", "Webhook callback URL the agent posts updates to (required)") + fl.StringVar(&f.id, "id", "", "Optional client-set configuration ID (allows multiple callbacks)") + fl.StringVar(&f.token, "token", "", "Optional token the agent echoes back so the receiver can validate calls") + fl.StringVar(&f.authScheme, "auth-scheme", "", "Optional auth scheme the agent uses when calling the webhook (e.g. Bearer)") + fl.StringVar(&f.authCredentials, "auth-credentials", "", "Optional credentials the agent presents to the webhook") + return cmd +} + +func buildPushConfig(f pushConfigCreateFlags) (*a2a.PushConfig, error) { + if f.url == "" { + return nil, fmt.Errorf("--url is required") + } + pc := &a2a.PushConfig{ + Tenant: f.tenant, + TaskID: a2a.TaskID(f.taskID), + ID: f.id, + Token: f.token, + URL: f.url, + } + if f.authScheme != "" || f.authCredentials != "" { + if f.authScheme == "" { + return nil, fmt.Errorf("--auth-scheme is required when --auth-credentials is set") + } + pc.Auth = &a2a.PushAuthInfo{Scheme: f.authScheme, Credentials: f.authCredentials} + } + return pc, nil +} diff --git a/internal/cli/task_push_config_delete.go b/internal/cli/task_push_config_delete.go new file mode 100644 index 0000000..74061ab --- /dev/null +++ b/internal/cli/task_push_config_delete.go @@ -0,0 +1,52 @@ +// Copyright 2026 The A2A Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cli + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "github.com/a2aproject/a2a-go/v2/a2a" +) + +func newPushConfigDeleteCmd(cfg *globalConfig) *cobra.Command { + return &cobra.Command{ + Use: "delete ", + Short: "Delete a task's push-notification configuration", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + ctx, cancel := context.WithTimeout(cmd.Context(), cfg.timeout) + defer cancel() + ctx = withServiceParams(ctx, cfg) + + client, err := newAgentClient(ctx, cfg) + if err != nil { + return fmt.Errorf("failed to create a client: %w", err) + } + defer destroyClient(cfg, client) + + if err := client.DeleteTaskPushConfig(ctx, &a2a.DeleteTaskPushConfigRequest{ + Tenant: cfg.tenant, + TaskID: a2a.TaskID(args[0]), + ID: args[1], + }); err != nil { + return fmt.Errorf("failed to delete push config %s: %w", args[1], err) + } + return cfg.PrintPushConfigDeleted(args[0], args[1]) + }, + } +} diff --git a/internal/cli/task_push_config_get.go b/internal/cli/task_push_config_get.go new file mode 100644 index 0000000..0c5d586 --- /dev/null +++ b/internal/cli/task_push_config_get.go @@ -0,0 +1,56 @@ +// Copyright 2026 The A2A Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cli + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "github.com/a2aproject/a2a-go/v2/a2a" +) + +func newPushConfigGetCmd(cfg *globalConfig) *cobra.Command { + return &cobra.Command{ + Use: "get ", + Short: "Get a task's push-notification configuration", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + ctx, cancel := context.WithTimeout(cmd.Context(), cfg.timeout) + defer cancel() + ctx = withServiceParams(ctx, cfg) + + client, err := newAgentClient(ctx, cfg) + if err != nil { + return fmt.Errorf("failed to create a client: %w", err) + } + defer destroyClient(cfg, client) + + result, err := client.GetTaskPushConfig(ctx, &a2a.GetTaskPushConfigRequest{ + Tenant: cfg.tenant, + TaskID: a2a.TaskID(args[0]), + ID: args[1], + }) + if err != nil { + return fmt.Errorf("failed to get push config %s: %w", args[1], err) + } + if err := cfg.PrintPushConfig(result); err != nil { + return fmt.Errorf("failed to print push config: %w", err) + } + return nil + }, + } +} diff --git a/internal/cli/task_push_config_list.go b/internal/cli/task_push_config_list.go new file mode 100644 index 0000000..e8f6594 --- /dev/null +++ b/internal/cli/task_push_config_list.go @@ -0,0 +1,67 @@ +// Copyright 2026 The A2A Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cli + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "github.com/a2aproject/a2a-go/v2/a2a" +) + +func newPushConfigListCmd(cfg *globalConfig) *cobra.Command { + var ( + limit int + pageToken string + ) + + cmd := &cobra.Command{ + Use: "list ", + Short: "List a task's push-notification configurations", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + ctx, cancel := context.WithTimeout(cmd.Context(), cfg.timeout) + defer cancel() + ctx = withServiceParams(ctx, cfg) + + client, err := newAgentClient(ctx, cfg) + if err != nil { + return fmt.Errorf("failed to create a client: %w", err) + } + defer destroyClient(cfg, client) + + configs, err := client.ListTaskPushConfigs(ctx, &a2a.ListTaskPushConfigRequest{ + Tenant: cfg.tenant, + TaskID: a2a.TaskID(args[0]), + PageSize: limit, + PageToken: pageToken, + }) + if err != nil { + return fmt.Errorf("failed to list push configs: %w", err) + } + if err := cfg.PrintPushConfigList(configs); err != nil { + return fmt.Errorf("failed to print push configs: %w", err) + } + return nil + }, + } + + f := cmd.Flags() + f.IntVar(&limit, "limit", 0, "Page size") + f.StringVar(&pageToken, "page-token", "", "Pagination token") + return cmd +} diff --git a/internal/cli/task_push_config_test.go b/internal/cli/task_push_config_test.go new file mode 100644 index 0000000..0c9a057 --- /dev/null +++ b/internal/cli/task_push_config_test.go @@ -0,0 +1,348 @@ +// Copyright 2026 The A2A Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cli + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/a2aproject/a2a-cli/internal/localsrv" + "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/a2aproject/a2a-go/v2/a2asrv" + "github.com/a2aproject/a2a-go/v2/a2asrv/push" +) + +func TestBuildPushConfig(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + in pushConfigCreateFlags + want *a2a.PushConfig + wantErr bool + }{ + { + name: "all fields", + in: pushConfigCreateFlags{ + taskID: "task-1", + tenant: "acme", + url: "https://hook.example/cb", + id: "cfg-1", + token: "tok", + authScheme: "Bearer", + authCredentials: "secret", + }, + want: &a2a.PushConfig{ + Tenant: "acme", TaskID: "task-1", ID: "cfg-1", Token: "tok", + URL: "https://hook.example/cb", + Auth: &a2a.PushAuthInfo{Scheme: "Bearer", Credentials: "secret"}, + }, + }, + { + name: "url only", + in: pushConfigCreateFlags{taskID: "task-1", url: "https://hook.example/cb"}, + want: &a2a.PushConfig{TaskID: "task-1", URL: "https://hook.example/cb"}, + }, + { + name: "fail if url is missing", + in: pushConfigCreateFlags{taskID: "task-1"}, + wantErr: true, + }, + { + name: "fail if no credentials scheme", + in: pushConfigCreateFlags{taskID: "task-1", url: "https://hook.example/cb", authCredentials: "secret"}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got, err := buildPushConfig(tt.in) + if tt.wantErr { + if err == nil { + t.Fatalf("buildPushConfig(%+v) error = nil, want an error", tt.in) + } + return + } + if err != nil { + t.Fatalf("buildPushConfig(%+v) error = %v, want nil", tt.in, err) + } + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Fatalf("buildPushConfig() wrong result (-want +got) diff = %s", diff) + } + }) + } +} + +func TestPushConfigCreate(t *testing.T) { + t.Parallel() + url := startPushTestServer(t) + taskID := sendTestMessage(t, url, "setup") + + tests := []struct { + name string + args []string + want *a2a.PushConfig + wantErr bool + }{ + { + name: "creates config with all fields", + args: []string{"-a", url, string(taskID), "-o", "json", + "--url", "https://hook.example/cb", "--id", "cfg-1", "--token", "tok", + "--auth-scheme", "Bearer", "--auth-credentials", "secret"}, + want: &a2a.PushConfig{ + TaskID: taskID, ID: "cfg-1", Token: "tok", URL: "https://hook.example/cb", + Auth: &a2a.PushAuthInfo{Scheme: "Bearer", Credentials: "secret"}, + }, + }, + { + name: "missing --url fails", + args: []string{"-a", url, string(taskID)}, + wantErr: true, + }, + { + name: "missing task id fails", + args: []string{"-a", url, "--url", "https://hook.example/cb"}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + baseArgs := []string{"task", "push-config", "create"} + out, err := runCMD(t, append(baseArgs, tt.args...)...) + if tt.wantErr { + if err == nil { + t.Fatalf("runCMD(%v) error = nil, want an error", tt.args) + } + return + } + if err != nil { + t.Fatalf("runCMD(%v) error = %v, want nil", tt.args, err) + } + var got a2a.PushConfig + if err := json.Unmarshal([]byte(out), &got); err != nil { + t.Fatalf("json.Unmarshal(push-config create output) error = %v", err) + } + if diff := cmp.Diff(*tt.want, got); diff != "" { + t.Fatalf("a2a push-config create wrong result (-want +got) diff = %s", diff) + } + }) + } +} + +func TestPushConfigGet(t *testing.T) { + t.Parallel() + url := startPushTestServer(t) + taskID := sendTestMessage(t, url, "setup") + created := createTestPushConfig(t, url, taskID) + + tests := []struct { + name string + args []string + want *a2a.PushConfig + wantErr bool + }{ + { + name: "gets config by id", + args: []string{"-a", url, string(taskID), created.ID, "-o", "json"}, + want: &created, + }, + { + name: "unknown config id fails", + args: []string{"-a", url, string(taskID), "does-not-exist"}, + wantErr: true, + }, + { + name: "missing config id fails", + args: []string{"-a", url, string(taskID)}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + baseArgs := []string{"task", "push-config", "get"} + out, err := runCMD(t, append(baseArgs, tt.args...)...) + if tt.wantErr { + if err == nil { + t.Fatalf("runCMD(%v) error = nil, want an error", tt.args) + } + return + } + if err != nil { + t.Fatalf("runCMD(%v) error = %v, want nil", tt.args, err) + } + var got a2a.PushConfig + if err := json.Unmarshal([]byte(out), &got); err != nil { + t.Fatalf("json.Unmarshal(push-config get output) error = %v", err) + } + if diff := cmp.Diff(*tt.want, got); diff != "" { + t.Fatalf("a2a push-config get wrong result (-want +got) diff = %s", diff) + } + }) + } +} + +func TestPushConfigList(t *testing.T) { + t.Parallel() + url := startPushTestServer(t) + taskID := sendTestMessage(t, url, "setup") + first := createTestPushConfig(t, url, taskID) + second := createTestPushConfig(t, url, taskID) + + tests := []struct { + name string + args []string + want []*a2a.PushConfig + wantErr bool + }{ + { + name: "lists all configs for a task", + args: []string{"-a", url, string(taskID), "-o", "json"}, + want: []*a2a.PushConfig{&first, &second}, + }, + { + name: "missing task id fails", + args: []string{"-a", url}, + wantErr: true, + }, + } + + sortByID := cmpopts.SortSlices(func(a, b *a2a.PushConfig) bool { return a.ID < b.ID }) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + baseArgs := []string{"task", "push-config", "list"} + out, err := runCMD(t, append(baseArgs, tt.args...)...) + if tt.wantErr { + if err == nil { + t.Fatalf("runCMD(%v) error = nil, want an error", tt.args) + } + return + } + if err != nil { + t.Fatalf("runCMD(%v) error = %v, want nil", tt.args, err) + } + var got []*a2a.PushConfig + if err := json.Unmarshal([]byte(out), &got); err != nil { + t.Fatalf("json.Unmarshal(push-config list output) error = %v", err) + } + if diff := cmp.Diff(tt.want, got, sortByID); diff != "" { + t.Fatalf("a2a push-config list wrong result (-want +got) diff = %s", diff) + } + }) + } +} + +func TestPushConfigDelete(t *testing.T) { + t.Parallel() + url := startPushTestServer(t) + taskID := sendTestMessage(t, url, "setup") + created := createTestPushConfig(t, url, taskID) + + type deleteResponse struct { + Deleted bool `json:"deleted"` + TaskID string `json:"taskId"` + ID string `json:"id"` + } + + tests := []struct { + name string + args []string + want deleteResponse + wantErr bool + }{ + { + name: "deletes config", + args: []string{"-a", url, string(taskID), created.ID, "-o", "json"}, + want: deleteResponse{Deleted: true, TaskID: string(taskID), ID: created.ID}, + }, + { + name: "missing config id fails", + args: []string{"-a", url, string(taskID)}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + baseArgs := []string{"task", "push-config", "delete"} + out, err := runCMD(t, append(baseArgs, tt.args...)...) + if tt.wantErr { + if err == nil { + t.Fatalf("runCMD(%v) error = nil, want an error", tt.args) + } + return + } + if err != nil { + t.Fatalf("runCMD(%v) error = %v, want nil", tt.args, err) + } + var got deleteResponse + if err := json.Unmarshal([]byte(out), &got); err != nil { + t.Fatalf("json.Unmarshal(push-config delete output) error = %v", err) + } + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Fatalf("a2a push-config delete wrong result (-want +got) diff = %s", diff) + } + }) + } +} + +func createTestPushConfig(t *testing.T, url string, taskID a2a.TaskID) a2a.PushConfig { + t.Helper() + out := mustRunCMD(t, "task", "push-config", "create", "-a", url, string(taskID), "-o", "json", + "--url", "https://hook.example/cb") + var pc a2a.PushConfig + if err := json.Unmarshal([]byte(out), &pc); err != nil { + t.Fatalf("json.Unmarshal(push-config create output) error = %v", err) + } + return pc +} + +func startPushTestServer(t *testing.T) string { + t.Helper() + + capabilities := a2a.AgentCapabilities{Streaming: true, PushNotifications: true} + handler := a2asrv.NewHandler( + localsrv.NewEchoExecutor(), + a2asrv.WithCapabilityChecks(&capabilities), + a2asrv.WithPushNotifications(push.NewInMemoryStore(), push.NewHTTPPushSender(nil)), + ) + + mux := http.NewServeMux() + mux.Handle("/", a2asrv.NewRESTHandler(handler)) + + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + mux.Handle(a2asrv.WellKnownAgentCardPath, a2asrv.NewStaticAgentCardHandler(&a2a.AgentCard{ + Name: "Test Echo", + Version: "1.0.0", + Capabilities: capabilities, + SupportedInterfaces: []*a2a.AgentInterface{a2a.NewAgentInterface(server.URL, a2a.TransportProtocolHTTPJSON)}, + })) + + return server.URL +} diff --git a/internal/output/output.go b/internal/output/output.go index 7a50df5..14bf4b7 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -139,6 +139,63 @@ func (p *Printer) PrintTaskList(resp *a2a.ListTasksResponse) error { return err } +// PrintPushConfig writes a single push-notification configuration in the +// configured Mode. In text mode the auth credentials are redacted. +func (p *Printer) PrintPushConfig(pc *a2a.PushConfig) error { + if p.Mode == ModeJson { + return p.PrintJSON(pc) + } + _, err := io.WriteString(p.Out, formatPushConfig(pc)) + return err +} + +// PrintPushConfigList writes a list of push-notification configurations in the +// configured Mode. +func (p *Printer) PrintPushConfigList(configs []*a2a.PushConfig) error { + if p.Mode == ModeJson { + return p.PrintJSON(configs) + } + tw := tabwriter.NewWriter(p.Out, 0, 4, 2, ' ', 0) + if _, err := io.WriteString(tw, "ID\tTASK\tURL\n"); err != nil { + return err + } + for _, pc := range configs { + if _, err := fmt.Fprintf(tw, "%s\t%s\t%s\n", pc.ID, pc.TaskID, pc.URL); err != nil { + return err + } + } + return tw.Flush() +} + +// PrintPushConfigDeleted confirms deletion of a push-notification configuration. +func (p *Printer) PrintPushConfigDeleted(taskID, configID string) error { + if p.Mode == ModeJson { + return p.PrintJSON(map[string]any{"deleted": true, "taskId": taskID, "id": configID}) + } + _, err := fmt.Fprintf(p.Out, "Deleted: %s (task %s)\n", configID, taskID) + return err +} + +func formatPushConfig(pc *a2a.PushConfig) string { + var sb strings.Builder + if pc.ID != "" { + fmt.Fprintf(&sb, "Config: %s\n", pc.ID) + } + fmt.Fprintf(&sb, "Task: %s\n", pc.TaskID) + fmt.Fprintf(&sb, "URL: %s\n", pc.URL) + if pc.Token != "" { + fmt.Fprintf(&sb, "Token: %s\n", pc.Token) + } + if pc.Auth != nil { + fmt.Fprintf(&sb, "Auth: %s", pc.Auth.Scheme) + if pc.Auth.Credentials != "" { + sb.WriteString(" ") + } + sb.WriteString("\n") + } + return sb.String() +} + func formatCard(card *a2a.AgentCard) string { var sb strings.Builder fmt.Fprintf(&sb, "Name: %s\n", card.Name)