# AG-UI Client and Server Sample

This sample demonstrates how to use the AG-UI (Agent UI) protocol to enable communication between a client application and a remote agent server. The AG-UI protocol provides a standardized way for clients to interact with AI agents.

## Overview

The demonstration has two components:

1. **AGUIServer** - An ASP.NET Core web server that hosts an AI agent and exposes it via the AG-UI protocol
2. **AGUIClient** - A console application that connects to the AG-UI server and displays streaming updates

> **Warning**
> The AG-UI protocol is still under development and changing.
> We will try to keep these samples updated as the protocol evolves.

## Configuring Environment Variables

Configure the required Azure OpenAI environment variables:

```powershell
$env:AZURE_OPENAI_ENDPOINT="<<your-model-endpoint>>"
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini"
```

> **Note:** This sample uses `DefaultAzureCredential` for authentication. Make sure you're authenticated with Azure (e.g., via `az login`, Visual Studio, or environment variables).

## Running the Sample

### Step 1: Start the AG-UI Server

```bash
cd AGUIServer
dotnet build
dotnet run --urls "http://localhost:5100"
```

The server will start and listen on `http://localhost:5100`.

### Step 2: Testing with the REST Client (Optional)

Before running the client, you can test the server using the included `.http` file:

1. Open [./AGUIServer/AGUIServer.http](./AGUIServer/AGUIServer.http) in Visual Studio or VS Code with the REST Client extension
2. Send a test request to verify the server is working
3. Observe the server-sent events stream in the response

Sample request:
```http
POST http://localhost:5100/
Content-Type: application/json

{
  "threadId": "thread_123",
  "runId": "run_456",
  "messages": [
    {
      "role": "user",
      "content": "What is the capital of France?"
    }
  ],
  "context": {}
}
```

### Step 3: Run the AG-UI Client

In a new terminal window:

```bash
cd AGUIClient
dotnet run
```

Optionally, configure a different server URL:

```powershell
$env:AGUI_SERVER_URL="http://localhost:5100"
```

### Step 4: Interact with the Agent

1. The client will connect to the AG-UI server
2. Enter your message at the prompt
3. Observe the streaming updates with color-coded output:
   - **Yellow**: Run started notification showing thread and run IDs
   - **Cyan**: Agent's text response (streamed character by character)
   - **Green**: Run finished notification
   - **Red**: Error messages (if any occur)
4. Type `:q` or `quit` to exit

## Sample Output

```
AGUIClient> dotnet run
info: AGUIClient[0]
      Connecting to AG-UI server at: http://localhost:5100

User (:q or quit to exit): What is the capital of France?

[Run Started - Thread: thread_abc123, Run: run_xyz789]
The capital of France is Paris. It is known for its rich history, culture, and iconic landmarks such as the Eiffel Tower and the Louvre Museum.
[Run Finished - Thread: thread_abc123, Run: run_xyz789]

User (:q or quit to exit): Tell me a fun fact about space

[Run Started - Thread: thread_abc123, Run: run_def456]
Here's a fun fact: A day on Venus is longer than its year! Venus takes about 243 Earth days to rotate once on its axis, but only about 225 Earth days to orbit the Sun.
[Run Finished - Thread: thread_abc123, Run: run_def456]

User (:q or quit to exit): :q
```

## How It Works

### Server Side

The `AGUIServer` uses the `MapAGUIServer` extension method to expose an agent through the AG-UI protocol:

```csharp
AIAgent agent = new OpenAIClient(apiKey)
    .GetChatClient(model)
    .AsAIAgent(
        instructions: "You are a helpful assistant.",
        name: "AGUIAssistant");

app.MapAGUIServer("/", agent);
```

This automatically handles:
- HTTP POST requests with message payloads
- Converting agent responses to AG-UI event streams
- Server-sent events (SSE) formatting
- Thread and run management

### Client Side

The `AGUIClient` uses the `AGUIChatClient` to connect to the remote server:

```csharp
using HttpClient httpClient = new();
var chatClient = new AGUIChatClient(new(httpClient, serverUrl));

AIAgent agent = chatClient.AsAIAgent(
    instructions: null,
    name: "agui-client",
    description: "AG-UI Client Agent",
    tools: []);

bool isFirstUpdate = true;
AgentResponseUpdate? currentUpdate = null;
string? threadId = null;

await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread))
{
    // AGUIChatClient is stateless and never surfaces a ConversationId; the thread id is
    // carried on the AG-UI RUN_STARTED event's raw representation.
    if (update.AsChatResponseUpdate().RawRepresentation is RunStartedEvent runStarted)
    {
        threadId = runStarted.ThreadId;
    }

    // First update indicates run started
    if (isFirstUpdate)
    {
        Console.WriteLine($"[Run Started - Thread: {threadId}, Run: {update.ResponseId}]");
        isFirstUpdate = false;
    }
    
    currentUpdate = update;
    
    foreach (AIContent content in update.Contents)
    {
        switch (content)
        {
            case TextContent textContent:
                // Display streaming text
                Console.Write(textContent.Text);
                break;
            case ErrorContent errorContent:
                // Display error notification
                Console.WriteLine($"[Error: {errorContent.Message}]");
                break;
        }
    }
}

// Last update indicates run finished
if (currentUpdate != null)
{
    Console.WriteLine($"\n[Run Finished - Thread: {threadId}, Run: {currentUpdate.ResponseId}]");
}
```

The `RunStreamingAsync` method:
1. Sends messages to the server via HTTP POST
2. Receives server-sent events (SSE) stream
3. Parses events into `AgentResponseUpdate` objects
4. Yields updates as they arrive for real-time display

## Key Concepts

- **Thread**: Represents a conversation context that persists across multiple runs. `AGUIChatClient` is stateless and does not surface a `ConversationId`; the thread id is read from the `RUN_STARTED`/`RUN_FINISHED` event's raw representation (`RunStartedEvent.ThreadId`). Continuation is driven by resending the full message history (and, to branch from a prior run, setting `RunAgentInput.ThreadId`/`ParentRunId` via `ChatOptions.RawRepresentationFactory`).
- **Run**: A single execution of the agent for a given set of messages (identified by `ResponseId` property)
- **AgentResponseUpdate**: Contains the response data with:
  - `ResponseId`: The unique run identifier
  - `RawRepresentation`: The underlying AG-UI event (e.g. `RunStartedEvent`), which carries wire-level fields such as the thread id
  - `Contents`: Collection of content items (TextContent, ErrorContent, etc.)
- **Run Lifecycle**: 
  - The **first** `AgentResponseUpdate` in a run indicates the run has started
  - Subsequent updates contain streaming content as the agent processes
  - The **last** `AgentResponseUpdate` in a run indicates the run has finished
  - If an error occurs, the update will contain `ErrorContent`