Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
d50af9e
feat: support agui
Flash-LHR Sep 18, 2025
c4ec52f
refactor
Flash-LHR Sep 19, 2025
f6d0c59
Merge branch 'main' into support-agui
Flash-LHR Sep 19, 2025
067afd5
fix
Flash-LHR Sep 19, 2025
b83f997
feat: support DefaultNewService
Flash-LHR Sep 22, 2025
d1bf559
Merge branch 'main' into support-agui
Flash-LHR Sep 22, 2025
ba011eb
feat: adapter
Flash-LHR Sep 25, 2025
dc7c5af
feat: support non-stream message
Flash-LHR Sep 25, 2025
b939e00
fix
Flash-LHR Sep 25, 2025
dad2ebf
example: add copilokit
Flash-LHR Sep 25, 2025
376680b
docs
Flash-LHR Sep 25, 2025
8f7861d
doc
Flash-LHR Sep 25, 2025
e1214a4
front
Flash-LHR Sep 25, 2025
e5bdc2d
doc
Flash-LHR Sep 25, 2025
2d90ab0
doc
Flash-LHR Sep 25, 2025
e9a15c6
Merge branch 'main' into support-agui
Flash-LHR Sep 25, 2025
6b246c2
feat: use Handler
Flash-LHR Sep 26, 2025
5361c06
refactor
Flash-LHR Sep 26, 2025
d05620b
only post
Flash-LHR Sep 26, 2025
cbe1424
fix
Flash-LHR Sep 26, 2025
13f40ec
test
Flash-LHR Sep 26, 2025
5e2e01e
fix
Flash-LHR Sep 26, 2025
3113f81
doc
Flash-LHR Sep 26, 2025
a31d209
Merge branch 'main' into support-agui
Flash-LHR Sep 26, 2025
be3e701
typo
Flash-LHR Sep 26, 2025
6333979
fix
Flash-LHR Sep 26, 2025
a4cd818
docs
Flash-LHR Sep 26, 2025
e95347d
fix
Flash-LHR Sep 26, 2025
afec996
refactor: runner
Flash-LHR Sep 26, 2025
f5fa1a4
Revert "refactor: runner"
Flash-LHR Sep 26, 2025
613c887
docs
Flash-LHR Sep 28, 2025
390ccce
doc
Flash-LHR Sep 28, 2025
783b880
example
Flash-LHR Sep 28, 2025
2a3ed72
refactor: runner instead of agent
Flash-LHR Sep 28, 2025
2fa5e13
test
Flash-LHR Sep 28, 2025
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
27 changes: 27 additions & 0 deletions examples/agui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# AG-UI Examples

This folder collects runnable demos that showcase how to integrate the `tRPC-Agent-Go` AG-UI server and various clients.

- [`client/`](client/) – Client-side samples.
- [`server/`](server/) – Server-side samples.

## Quick Start

1. Start the default AG-UI server:

```bash
go run ./server/default
```

2. In another terminal start the Bubble Tea client:

```bash
go run ./client/bubbletea/main.go
```

3. Ask a question such as `calculate 1.2+3.5` and watch the live event stream in
the terminal. A full transcript example is documented in
[`client/bubbletea/README.md`](client/bubbletea/README.md).

See the individual README files under `client/` and `server/` for more background
and configuration options.
7 changes: 7 additions & 0 deletions examples/agui/client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# AG-UI Clients

This directory collects runnable clients that can talk to the AG-UI SSE server examples.

## Available Clients

- [bubbletea/](bubbletea/) – Terminal interface built with Bubble Tea. Streams events as they arrive so you can watch agent reasoning in real time.
54 changes: 54 additions & 0 deletions examples/agui/client/bubbletea/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Bubble Tea AG-UI Client

This sample uses [Bubble Tea](https://github.com/charmbracelet/bubbletea) to present a terminal chat UI that consumes the AG-UI SSE stream exposed by the example server. Events are rendered as soon as they arrive so you can watch the agent reason step by step.

## Run the Client

From `examples/agui`:

```bash
go run ./client/bubbletea/
```

You can customise the endpoint with `--endpoint` if the server is hosted elsewhere.

## Sample Output

The client streams every AG-UI event. Submitting `calculate 1.2+3.5` produces output like the following (truncated IDs for clarity):

```text
Simple AG-UI Client. Press Ctrl+C to quit.
You> calculate 1.2+3.5
Agent> [RUN_STARTED]
Agent> [TEXT_MESSAGE_START]
Agent> [TEXT_MESSAGE_CONTENT] 我来
Agent> [TEXT_MESSAGE_CONTENT] 帮
Agent> [TEXT_MESSAGE_CONTENT] 您
Agent> [TEXT_MESSAGE_CONTENT] 计算
Agent> [TEXT_MESSAGE_CONTENT] 1
Agent> [TEXT_MESSAGE_CONTENT] .
Agent> [TEXT_MESSAGE_CONTENT] 2
Agent> [TEXT_MESSAGE_CONTENT] +
Agent> [TEXT_MESSAGE_CONTENT] 3
Agent> [TEXT_MESSAGE_CONTENT] .
Agent> [TEXT_MESSAGE_CONTENT] 5
Agent> [TEXT_MESSAGE_CONTENT] 。
Agent> [TOOL_CALL_START] tool call 'calculator' started, id: call_...
Agent> [TOOL_CALL_ARGS] tool args: {"a":1.2,"b":3.5,"operation":"plus"}
Agent> [TOOL_CALL_END] tool call completed, id: call_...
Agent> [TOOL_CALL_RESULT] tool result: {"result":4.7}
Agent> [TEXT_MESSAGE_START]
Agent> [TEXT_MESSAGE_CONTENT] 1
Agent> [TEXT_MESSAGE_CONTENT] .
Agent> [TEXT_MESSAGE_CONTENT] 2
Agent> [TEXT_MESSAGE_CONTENT] +
Agent> [TEXT_MESSAGE_CONTENT] 3
Agent> [TEXT_MESSAGE_CONTENT] .
Agent> [TEXT_MESSAGE_CONTENT] 5
Agent> [TEXT_MESSAGE_CONTENT] =
Agent> [TEXT_MESSAGE_CONTENT] 4
Agent> [TEXT_MESSAGE_CONTENT] .
Agent> [TEXT_MESSAGE_CONTENT] 7
Agent> [TEXT_MESSAGE_END]
Agent> [RUN_FINISHED]
```
158 changes: 158 additions & 0 deletions examples/agui/client/bubbletea/agui.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package main

import (
"context"
"fmt"
"strings"
"sync"
"time"

"github.com/ag-ui-protocol/ag-ui/sdks/community/go/pkg/client/sse"
"github.com/ag-ui-protocol/ag-ui/sdks/community/go/pkg/core/events"
tea "github.com/charmbracelet/bubbletea"
"github.com/sirupsen/logrus"
)

type errMsg struct {
error
}

type chatStreamReadyMsg struct {
stream *chatStream
}

type chatStreamEventMsg struct {
stream *chatStream
lines []string
}

type chatStreamFinishedMsg struct {
stream *chatStream
}

func startChatCmd(prompt, endpoint string) tea.Cmd {
return func() tea.Msg {
stream, err := openChatStream(prompt, endpoint)
if err != nil {
return errMsg{err}
}
return chatStreamReadyMsg{stream: stream}
}
}

type chatStream struct {
ctx context.Context
cancel context.CancelFunc
client *sse.Client
frames <-chan sse.Frame
errCh <-chan error
once sync.Once
}

func (s *chatStream) Close() {
if s == nil {
return
}
s.once.Do(func() {
s.cancel()
s.client.Close()
})
}

func openChatStream(prompt, endpoint string) (*chatStream, error) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
logger := logrus.New()
logger.SetLevel(logrus.FatalLevel)
client := sse.NewClient(sse.Config{
Endpoint: endpoint,
ConnectTimeout: 30 * time.Second,
ReadTimeout: 5 * time.Minute,
BufferSize: 100,
Logger: logger,
})
payload := map[string]any{
"threadId": "demo-thread",
"runId": fmt.Sprintf("run-%d", time.Now().UnixNano()),
"messages": []map[string]any{{"role": "user", "content": prompt}},
}
frames, errCh, err := client.Stream(sse.StreamOptions{Context: ctx, Payload: payload})
if err != nil {
cancel()
client.Close()
return nil, fmt.Errorf("failed to start SSE stream: %w", err)
}
return &chatStream{
ctx: ctx,
cancel: cancel,
client: client,
frames: frames,
errCh: errCh,
}, nil
}

func readNextEventCmd(stream *chatStream) tea.Cmd {
if stream == nil {
return nil
}
return func() tea.Msg {
for {
select {
case frame, ok := <-stream.frames:
if !ok {
stream.Close()
return chatStreamFinishedMsg{stream: stream}
}
evt, err := events.EventFromJSON(frame.Data)
if err != nil {
stream.Close()
return fmt.Errorf("parse event: %w", err)
}
lines := formatEvent(evt)
if len(lines) == 0 {
continue
}
return chatStreamEventMsg{stream: stream, lines: lines}
case err, ok := <-stream.errCh:
if !ok || err == nil {
continue
}
stream.Close()
return errMsg{err}
case <-stream.ctx.Done():
stream.Close()
return errMsg{stream.ctx.Err()}
}
}
}
}

func formatEvent(evt events.Event) []string {
label := fmt.Sprintf("[%s]", evt.Type())
switch e := evt.(type) {
case *events.RunStartedEvent:
return []string{fmt.Sprintf("Agent> %s", label)}
case *events.RunFinishedEvent:
return []string{fmt.Sprintf("Agent> %s", label)}
case *events.RunErrorEvent:
return []string{fmt.Sprintf("Agent> %s: %s", label, e.Message)}
case *events.TextMessageStartEvent:
return []string{fmt.Sprintf("Agent> %s", label)}
case *events.TextMessageContentEvent:
if strings.TrimSpace(e.Delta) == "" {
return nil
}
return []string{fmt.Sprintf("Agent> %s %s", label, e.Delta)}
case *events.TextMessageEndEvent:
return []string{fmt.Sprintf("Agent> %s", label)}
case *events.ToolCallStartEvent:
return []string{fmt.Sprintf("Agent> %s tool call '%s' started, id: %s", label, e.ToolCallName, e.ToolCallID)}
case *events.ToolCallArgsEvent:
return []string{fmt.Sprintf("Agent> %s tool args: %s", label, e.Delta)}
case *events.ToolCallEndEvent:
return []string{fmt.Sprintf("Agent> %s tool call completed, id: %s", label, e.ToolCallID)}
case *events.ToolCallResultEvent:
return []string{fmt.Sprintf("Agent> %s tool result: %s", label, e.Content)}
default:
return nil
}
}
20 changes: 20 additions & 0 deletions examples/agui/client/bubbletea/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"flag"
"log"

tea "github.com/charmbracelet/bubbletea"
)

func main() {
endpoint := flag.String("endpoint", "http://localhost:8080/agui", "AG-UI SSE endpoint")
flag.Parse()

if _, err := tea.NewProgram(
initialModel(*endpoint),
tea.WithAltScreen(),
).Run(); err != nil {
log.Fatalf("bubbletea program failed: %v", err)
}
}
Loading
Loading