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
5 changes: 5 additions & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,10 @@ runs:
path: |
~/.cache/go-build
~/go/pkg/mod
~/.cgo-cache
key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.mod', '**/go.sum') }}
restore-keys: ${{ runner.os }}-go-build-

- name: Install SLIM bindings native library
shell: bash
run: go run github.com/agntcy/slim-bindings-go/v2/cmd/slim-bindings-setup
4 changes: 4 additions & 0 deletions .github/workflows/go.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,13 @@ jobs:

- name: Build
run: go build -mod=readonly -v ./...
env:
CGO_ENABLED: "1"

- name: Test
run: go test -race -mod=readonly -v -count=1 -shuffle=on ./...
env:
CGO_ENABLED: "1"

lint:
runs-on: ubuntu-latest
Expand Down
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/a2aproject/a2a-cli

go 1.25.0
go 1.25.2

require (
github.com/a2aproject/a2a-go v0.3.15
Expand All @@ -13,6 +13,8 @@ require (
)

require (
github.com/agntcy/slim-a2a-go v0.3.0
github.com/agntcy/slim-bindings-go/v2 v2.1.1
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
golang.org/x/mod v0.35.0 // indirect
Expand All @@ -24,3 +26,5 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)

replace github.com/agntcy/slim-a2a-go => /Users/sambetts/Work/src/github.com/agntcy/slim-a2a-go
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/a2aproject/a2a-go v0.3.15 h1:h5YpCiPq3jxQ5rIns7oDjPag3ivP8u817AzdA4F+
github.com/a2aproject/a2a-go v0.3.15/go.mod h1:I7Cm+a1oL+UT6zMoP+roaRE5vdfUa1iQGVN8aSOuZ0I=
github.com/a2aproject/a2a-go/v2 v2.4.1-0.20260817112309-579cff89f7ba h1:4EedjlTSsl92mWP9qAfa8zKHI7mLzOkMpFiSHl9Y8u0=
github.com/a2aproject/a2a-go/v2 v2.4.1-0.20260817112309-579cff89f7ba/go.mod h1:EghJ/rY9OCC6jme1z+otOBf+1YCVVCpMSPZU31t3hKY=
github.com/agntcy/slim-bindings-go/v2 v2.1.1 h1:I2Ac5GRH7W+1gp3Vxm2me0wME0TVroPuK1uvkivp9t0=
github.com/agntcy/slim-bindings-go/v2 v2.1.1/go.mod h1:LfBa89/DfmYRg9EeU9DxkyXhJTBjsJ0OLIpxA1iBuhc=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
Expand Down
32 changes: 26 additions & 6 deletions internal/cli/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,13 @@ func newClientFromEndpoint(ctx context.Context, cfg *globalConfig, ref string, e
}
cfg.logf("connecting directly to %s via %s (skipping card resolution)", endpointURL, protocol)

factoryOpts, err := clientFactoryOpts(cfg)
if err != nil {
return nil, err
}

endpoint := a2a.NewAgentInterface(endpointURL, protocol)
client, err := a2aclient.NewFromEndpoints(ctx, []*a2a.AgentInterface{endpoint}, append(clientFactoryOpts(cfg), extraOpts...)...)
client, err := a2aclient.NewFromEndpoints(ctx, []*a2a.AgentInterface{endpoint}, append(factoryOpts, extraOpts...)...)
return client, hintInsecure(err)
}

Expand All @@ -90,7 +95,11 @@ func newClientFromCard(ctx context.Context, cfg *globalConfig, ref string, extra
return nil, fmt.Errorf("resolving agent card: %w", err)
}

factoryOpts := append(clientFactoryOpts(cfg), extraOpts...)
factoryOpts, err := clientFactoryOpts(cfg)
if err != nil {
return nil, err
}
factoryOpts = append(factoryOpts, extraOpts...)
if len(protos) > 0 {
factoryOpts = append(factoryOpts, a2aclient.WithConfig(a2aclient.Config{PreferredTransports: protos}))
}
Expand All @@ -107,22 +116,33 @@ func hintInsecure(err error) error {
return err
}

func clientFactoryOpts(cfg *globalConfig) []a2aclient.FactoryOption {
factoryOpts := []a2aclient.FactoryOption{
func clientFactoryOpts(cfg *globalConfig) ([]a2aclient.FactoryOption, error) {
opts := []a2aclient.FactoryOption{
a2av0.WithRESTTransport(a2av0.RESTTransportConfig{}),
a2av0.WithJSONRPCTransport(a2av0.JSONRPCTransportConfig{}),
}
var grpcOpts []grpc.DialOption
if cfg.insecureGRPC {
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
}
factoryOpts = append(factoryOpts,
opts = append(opts,
a2agrpcv0.WithGRPCTransport(grpcOpts...),
a2agrpc.WithGRPCTransport(grpcOpts...),
)
return factoryOpts
for _, t := range cfg.transports {
if strings.EqualFold(t, "slimrpc") {
slimOpt, err := withSLIMRPCTransport(cfg)
if err != nil {
return nil, err
}
opts = append(opts, slimOpt)
break
}
}
return opts, nil
}


func stripHTTPScheme(raw string) string {
u, err := url.Parse(raw)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
Expand Down
14 changes: 14 additions & 0 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ type globalConfig struct {
insecureGRPC bool
configPath string

// SLIM RPC transport options (only used when --transport slimrpc)
slimEndpoint string
slimLocalName string
slimIdentityProviderType string
slimIdentityProviderSharedSecret string
slimIdentityVerifierType string
slimIdentityVerifierSharedSecret string

bindings []clicfg.FlagBinding

*output.Printer
Expand Down Expand Up @@ -122,6 +130,12 @@ func newRootCmd(cfg *globalConfig, deps deps) *cobra.Command {
pf.BoolVarP(&cfg.verbose, "verbose", "v", false, "Verbose output to stderr")
pf.BoolVar(&cfg.insecureGRPC, "insecure", false, "Use insecure (plaintext) gRPC transport credentials")
pf.StringVar(&cfg.configPath, "config", "", "Load configuration from an explicit .env file in place of the local .env")
pf.StringVar(&cfg.slimEndpoint, "slim-endpoint", "http://127.0.0.1:46357", "SLIM node endpoint (used with --transport slimrpc)")
pf.StringVar(&cfg.slimLocalName, "slim-local-name", "agntcy/a2a-cli/client", "Local SLIM identity name org/namespace/app (used with --transport slimrpc)")
pf.StringVar(&cfg.slimIdentityProviderType, "slim-identity-provider-type", "", "SLIM identity provider type: sharedSecret (required with --transport slimrpc)")
pf.StringVar(&cfg.slimIdentityProviderSharedSecret, "slim-identity-provider-shared-secret", "", "Shared secret for the SLIM identity provider (used when --slim-identity-provider-type=sharedSecret)")
pf.StringVar(&cfg.slimIdentityVerifierType, "slim-identity-verifier-type", "", "SLIM identity verifier type: sharedSecret (required with --transport slimrpc)")
pf.StringVar(&cfg.slimIdentityVerifierSharedSecret, "slim-identity-verifier-shared-secret", "", "Shared secret for the SLIM identity verifier (used when --slim-identity-verifier-type=sharedSecret)")

cmd.AddCommand(
newCardCmd(cfg),
Expand Down
85 changes: 85 additions & 0 deletions internal/cli/slim.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//go:build cgo

// 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 (
"fmt"

"github.com/a2aproject/a2a-go/v2/a2aclient"
a2aslimrpc "github.com/agntcy/slim-a2a-go/a2aslimrpc/v1"
slim_bindings "github.com/agntcy/slim-bindings-go/v2"
)

func withSLIMRPCTransport(cfg *globalConfig) (a2aclient.FactoryOption, error) {
localName, err := slim_bindings.NameFromString(cfg.slimLocalName)
if err != nil {
return nil, fmt.Errorf("--slim-local-name: %w", err)
}

identityProvider, err := slimIdentityProvider(cfg)
if err != nil {
return nil, err
}

identityVerifier, err := slimIdentityVerifier(cfg)
if err != nil {
return nil, err
}

slim_bindings.InitializeWithDefaults()
svc := slim_bindings.GetGlobalService()

app, err := svc.CreateApp(localName, identityProvider, identityVerifier)
if err != nil {
return nil, fmt.Errorf("slim: create app: %w", err)
}

connID, err := svc.Connect(slim_bindings.NewInsecureClientConfig(cfg.slimEndpoint))
if err != nil {
return nil, fmt.Errorf("slim: connect to %s: %w", cfg.slimEndpoint, err)
}

if err := app.Subscribe(localName, &connID); err != nil {
return nil, fmt.Errorf("slim: subscribe: %w", err)
}

cfg.logf("connected to SLIM node at %s as %s", cfg.slimEndpoint, cfg.slimLocalName)

return a2aslimrpc.WithSLIMRPCTransport(app, &connID), nil
}

func slimIdentityProvider(cfg *globalConfig) (slim_bindings.IdentityProviderConfig, error) {
switch cfg.slimIdentityProviderType {
case "sharedSecret":
return slim_bindings.IdentityProviderConfigSharedSecret{Data: cfg.slimIdentityProviderSharedSecret}, nil
case "":
return nil, fmt.Errorf("--slim-identity-provider-type is required with --transport slimrpc (use sharedSecret)")
default:
return nil, fmt.Errorf("--slim-identity-provider-type: unknown type %q (use sharedSecret)", cfg.slimIdentityProviderType)
}
}

func slimIdentityVerifier(cfg *globalConfig) (slim_bindings.IdentityVerifierConfig, error) {
switch cfg.slimIdentityVerifierType {
case "sharedSecret":
return slim_bindings.IdentityVerifierConfigSharedSecret{Data: cfg.slimIdentityVerifierSharedSecret}, nil
case "":
return nil, fmt.Errorf("--slim-identity-verifier-type is required with --transport slimrpc (use sharedSecret)")
default:
return nil, fmt.Errorf("--slim-identity-verifier-type: unknown type %q (use sharedSecret)", cfg.slimIdentityVerifierType)
}
}
27 changes: 27 additions & 0 deletions internal/cli/slim_nocgo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//go:build !cgo

// 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 (
"fmt"

"github.com/a2aproject/a2a-go/v2/a2aclient"
)

func withSLIMRPCTransport(_ *globalConfig) (a2aclient.FactoryOption, error) {
return nil, fmt.Errorf("slimrpc transport requires CGO; rebuild without CGO_ENABLED=0")
}
6 changes: 4 additions & 2 deletions internal/flagparse/transports.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func SingleTransport(ss []string) (a2a.TransportProtocol, error) {
return "", err
}
if len(protos) != 1 {
return "", fmt.Errorf("exactly one --transport is required (rest, jsonrpc, or grpc)")
return "", fmt.Errorf("exactly one --transport is required (rest, jsonrpc, grpc, or slimrpc)")
}
return protos[0], nil
}
Expand All @@ -59,7 +59,9 @@ func parseTransport(s string) (a2a.TransportProtocol, error) {
return a2a.TransportProtocolJSONRPC, nil
case "grpc":
return a2a.TransportProtocolGRPC, nil
case "slimrpc":
return a2a.TransportProtocol("slimrpc"), nil
default:
return "", fmt.Errorf("unknown transport %q (use rest, jsonrpc, or grpc)", s)
return "", fmt.Errorf("unknown transport %q (use rest, jsonrpc, grpc, or slimrpc)", s)
}
}
Loading