{"id":46328081,"url":"https://github.com/alis-exchange/a2a-playground","last_synced_at":"2026-03-04T17:01:10.949Z","repository":{"id":337794329,"uuid":"1155215668","full_name":"alis-exchange/a2a-playground","owner":"alis-exchange","description":"A local CLI that serves a Vue UI and proxies to an A2A (Agent-to-Agent) agent. Use it to test and interact with A2A agents in your browser. Supports both gRPC and JSON-RPC transports.","archived":false,"fork":false,"pushed_at":"2026-02-20T08:06:20.000Z","size":14796,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-20T11:53:18.395Z","etag":null,"topics":["a2a","a2a-agent","a2a-client","a2a-protocol"],"latest_commit_sha":null,"homepage":"https://github.com/alis-exchange/a2a-playground","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alis-exchange.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-11T08:59:57.000Z","updated_at":"2026-02-20T08:06:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/alis-exchange/a2a-playground","commit_stats":null,"previous_names":["alis-exchange/a2a-playground"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/alis-exchange/a2a-playground","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alis-exchange%2Fa2a-playground","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alis-exchange%2Fa2a-playground/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alis-exchange%2Fa2a-playground/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alis-exchange%2Fa2a-playground/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alis-exchange","download_url":"https://codeload.github.com/alis-exchange/a2a-playground/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alis-exchange%2Fa2a-playground/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30086513,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-04T15:40:14.053Z","status":"ssl_error","status_checked_at":"2026-03-04T15:40:13.655Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["a2a","a2a-agent","a2a-client","a2a-protocol"],"created_at":"2026-03-04T17:01:09.050Z","updated_at":"2026-03-04T17:01:10.933Z","avatar_url":"https://github.com/alis-exchange.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# a2a-playground\n\nA local CLI that serves a Vue UI and proxies to an A2A (Agent-to-Agent) agent. Use it to test and interact with A2A agents in your browser. Supports both **gRPC** and **JSON-RPC** transports.\n\n## Quick start\n\nWith an A2A agent running (e.g. on port 8080), install and run:\n\n```bash\ngo install github.com/alis-exchange/a2a-playground/cmd/a2a-playground@latest\na2a-playground --agent-url=localhost:8080\n```\n\nThe browser opens at `http://localhost:3000`. Start chatting with your agent.\n\n## Installation\n\n### From release\n\nNo dependencies. Installs the binary for your platform:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/alis-exchange/a2a-playground/main/install.sh | bash\n```\n\nOr a specific version:\n\n```bash\n./install.sh -v v1.0.0\n```\n\n### With Go\n\nRequires **Go 1.25+**:\n\n```bash\ngo install github.com/alis-exchange/a2a-playground/cmd/a2a-playground@latest\n```\n\nOr a specific version:\n\n```bash\ngo install github.com/alis-exchange/a2a-playground/cmd/a2a-playground@v1.0.0\n```\n\nRun without installing (fetches and builds on first use):\n\n```bash\n# gRPC\ngo run github.com/alis-exchange/a2a-playground/cmd/a2a-playground@latest --agent-url=localhost:8080\n\n# JSON-RPC\ngo run github.com/alis-exchange/a2a-playground/cmd/a2a-playground@latest --agent-url=http://localhost:8080/jsonrpc --jsonrpc\n```\n\n\u003e **Note:** `go run` and `go install` with a version (e.g. `@v1.0.0`) only work for releases where the embedded frontend was committed. Use the [install script](#from-release) or [build from source](#from-source) if a version fails with \"no matching files found\".\n\n### From source\n\nRequires **Go 1.25+**, **Node.js 20+**, **pnpm**, and **buf** CLI:\n\n```bash\nmake build\n```\n\nThis generates protobuf code, builds the frontend, embeds it, and compiles the `a2a-playground` binary.\n\n## Usage\n\n### gRPC (default)\n\n```bash\na2a-playground --agent-url=localhost:8080\n```\n\n### JSON-RPC\n\n```bash\na2a-playground --agent-url=http://localhost:8080/jsonrpc --jsonrpc\n```\n\n### Flags\n\n| Flag          | Default                   | Description                                                                                          |\n| ------------- | ------------------------- | ---------------------------------------------------------------------------------------------------- |\n| `--agent-url` | `localhost:8080`          | Agent endpoint. For gRPC: `host:port`. For JSON-RPC: full URL (e.g. `http://localhost:8080/jsonrpc`) |\n| `--grpc`      | _(when no protocol flag)_ | Use gRPC transport (default)                                                                         |\n| `--jsonrpc`   | —                         | Use JSON-RPC over HTTP transport                                                                     |\n| `--port`      | `3000`                    | HTTP port for the BFF                                                                                |\n| `--no-open`   | `false`                   | Do not open the browser on start                                                                     |\n| `--dev`       | `false`                   | Serve from `app/dist` on disk instead of embedded files                                              |\n\n### Authentication and headers\n\nUse the **Playground** sidebar (right side) to configure:\n\n- **Connection settings** – Agent URL and transport (gRPC or JSON-RPC).\n- **Authentication** – Custom headers (e.g. `Authorization`, `X-API-Key`, `X-Tenant-ID`) sent with every request. These are persisted and forwarded by the BFF to the agent.\n- **OAuth 2.0** – Client ID, Client Secret, Authorization URL, Token URL, and scope. Click **Authorize Connection** to sign in via a popup; the access token is then sent as `Authorization: Bearer \u003ctoken\u003e` on all A2A requests. The BFF forwards the incoming `Authorization` header to the agent.\n\n## Architecture\n\n```\n┌─────────┐     ┌─────────────────────────────────────┐     ┌────────────────────────────┐\n│ Browser │────▶│ BFF (static SPA + Connect proxy)    │────▶│ gRPC or JSON-RPC A2A agent  │\n│         │     │ serves Vue app, proxies A2A RPC      │     │ (e.g. localhost:8080)      │\n└─────────┘     └─────────────────────────────────────┘     └────────────────────────────┘\n```\n\nThe BFF (Backend-for-Frontend) serves the static Vue SPA and proxies Connect-RPC requests to your A2A agent. You can use **gRPC** (default) or **JSON-RPC** depending on what your agent exposes.\n\n### Message flow (Frontend → BFF → Agent)\n\n```\n┌─────────────────────────────────────────────────────────────────────────────────────┐\n│ Frontend (Vue)                                                                      │\n│  messages.ts → createA2AClient().sendStreamingMessage(req)                          │\n│  a2aClient.ts: agentConfigInterceptor (X-A2A-Agent-URL, X-A2A-Agent-Protocol)       │\n│                agentOAuthBearerInterceptor (Authorization: Bearer \u003ctoken\u003e)           │\n│                agentHeadersInterceptor (X-A2A-Agent-Headers from Pinia store)        │\n└─────────────────────────────────────────────────────────────────────────────────────┘\n                                        │\n                                        │ HTTP POST (Connect) to /a2a.v1.A2AService/*\n                                        │ Headers: Authorization, X-A2A-Agent-Headers, etc.\n                                        ▼\n┌─────────────────────────────────────────────────────────────────────────────────────┐\n│ BFF                                                                                  │\n│  server.go: ExtractAgentHeaders(r) → WithAgentHeaders(ctx) → proxy handler          │\n│  Protocol switch: cfg.Protocol ? NewJSONRPCProxy : NewGrpcProxy                     │\n└─────────────────────────────────────────────────────────────────────────────────────┘\n                    │                                    │\n        ┌───────────┴───────────┐            ┌────────────┴────────────┐\n        │ grpcProxy              │            │ jsonrpcProxy             │\n        │ metadata.Append(...)   │            │ agentHeadersInterceptor  │\n        │ → gRPC agent           │            │ req.Meta.Append(...)     │\n        │ localhost:8080         │            │ → HTTP JSON-RPC agent    │\n        └───────────────────────┘            │ http://.../jsonrpc       │\n                                              └─────────────────────────┘\n```\n\n### How it works\n\n**1. Frontend**\n\n- User input flows through `PlaygroundView` into the messages store (`app/src/pages/playground/store/messages.ts`), which builds a `SendMessageRequest` and calls `createA2AClient().sendStreamingMessage(request)`.\n- The Connect client (`app/src/clients/a2aClient.ts`) uses same-origin requests, so all A2A calls go to the BFF (e.g. `http://localhost:3000`).\n- Before every request, the Connect client adds: (1) agent config (`X-A2A-Agent-URL`, `X-A2A-Agent-Protocol`) from `agentConnection` store; (2) `Authorization: Bearer \u003ctoken\u003e` from `agentOAuth` store when the user has completed OAuth; (3) custom headers as `X-A2A-Agent-Headers` (JSON) from `agentHeaders` store. These are configured in the Playground sidebar and (for headers) can be persisted to localStorage.\n\n**2. BFF routing and headers**\n\n- The BFF (`internal/bff/server.go`) mounts two handler groups: the A2A Connect proxy at `/a2a.v1.A2AService/` and the static SPA at `/`.\n- All A2A requests pass through middleware that builds the agent headers from the incoming request (`internal/bff/headers.go`): it parses `X-A2A-Agent-Headers` (JSON) and merges in the request’s `Authorization` header so OAuth Bearer tokens are forwarded. The result is put into the request context; the chosen proxy sends these headers to the agent.\n\n**3. Protocol switching**\n\n- The CLI (`cmd/a2a-playground/main.go`) sets `--jsonrpc` or defaults to gRPC. That value is passed into `ServerConfig.Protocol`.\n- `NewServer` checks `cfg.Protocol`: if JSON-RPC, it creates `NewJSONRPCProxy(cfg.AgentURL)`; otherwise `NewGrpcProxy(cfg.AgentURL)`. Both proxies implement `A2AServiceHandler`, so routing stays the same; only the outbound transport differs.\n\n**4. gRPC proxy**\n\n- `internal/bff/proxy.go`: The gRPC proxy uses `go.alis.build/client` to connect to the agent. Before each call, `withAgentHeaders(ctx)` reads headers from context and adds them to `metadata.NewOutgoingContext`. Those metadata entries become gRPC headers on the agent call. The proxy forwards Connect requests as native gRPC calls.\n\n**5. JSON-RPC proxy**\n\n- `internal/bff/jsonrpc_proxy.go`: The JSON-RPC proxy uses `a2aclient` from `a2a-go` with `WithJSONRPCTransport`. It converts Connect/protobuf requests to JSON-RPC using `pbconv.FromProto*` and `pbconv.ToProto*`. The proxy is built with `WithInterceptors(\u0026agentHeadersInterceptor{})`, which reads headers from context and appends them to `req.Meta`; the a2a-go client sends `Meta` as HTTP headers to the agent's JSON-RPC endpoint. `ListTasks` is not supported over JSON-RPC per the A2A spec and returns `CodeUnimplemented`.\n\n## Development\n\n### Run in dev mode\n\nUses `app/dist` on disk instead of embedded files (no rebuild needed after frontend changes):\n\n```bash\nmake run\n```\n\nThis runs with `--dev --agent-url=localhost:8080`.\n\n### Generate protobuf only\n\n```bash\nmake generate\n```\n\nRuns `buf generate` in `packages/a2a` (generates TS into `app/packages/a2a/protobuf` and Go into `gen/go`).\n\n### Frontend development\n\n```bash\ncd app \u0026\u0026 pnpm dev\n```\n\nBuild frontend for production:\n\n```bash\ncd app \u0026\u0026 pnpm build\n```\n\n## Releasing\n\nThe binary embeds `app/dist` at build time. For `go run` and `go install` with a version to work, the built frontend must be committed **before** tagging:\n\n```bash\nmake generate build-frontend\ngit add app/dist\ngit commit -m \"chore: embed frontend for v1.0.0\"\ngit tag v1.0.0\ngit push origin main --tags\n```\n\nThe release workflow then builds the cross-platform binaries and attaches them to the GitHub release.\n\n## Project structure\n\n| Directory             | Description                                                  |\n| --------------------- | ------------------------------------------------------------ |\n| `cmd/a2a-playground/` | CLI entrypoint                                               |\n| `internal/bff/`       | BFF server, static serving, Connect proxy (gRPC or JSON-RPC) |\n| `packages/a2a/`       | Proto definitions and buf config (A2A canonical)              |\n| `app/`                | Vue 3 + Vuetify SPA                                          |\n| `gen/go/`             | Generated Connect handlers (uses `a2a-go/a2apb`)              |\n\n## Contributing\n\n1. Fork and open a PR\n2. Use [conventional commits](https://www.conventionalcommits.org/)\n3. Run tests: `go test ./...`\n\n## License\n\nSee [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falis-exchange%2Fa2a-playground","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falis-exchange%2Fa2a-playground","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falis-exchange%2Fa2a-playground/lists"}