In this tutorial, you’ll learn how to resurrect one of the first chatbots in computer science history—the famous Eliza, originally created in the 1960s! This not only pays homage to computing’s past but also demonstrates how the Agent User Interaction Protocol (AG-UI) can integrate seamlessly with any protocol. Let’s get started.

Prerequisites

Make sure to have the following installed:

Running Eliza via Docker

Let’s run Eliza in a Docker container and connect to her via telnet.

# Pull and run the Eliza Telnet server
docker build -t eliza:latest https://docs.ag-ui.io/quickstart/eliza/Dockerfile
docker run --rm -it -p 2323:2323 eliza:latest

Once the container is running, we can connect to Eliza.

telnet localhost 2323

You should see Eliza greet you with:

$ telnet localhost 2323
Trying ::1...
Connected to localhost.
Escape character is '^]'.
=====================================================
ELIZA - The first chatbot (1966) by Joseph Weizenbaum
Running via Telnet - Type 'quit' to exit
=====================================================

Hello, I'm Eliza. How can I help you today?
> How are you?
What if I were ?
> What if you were?
What does this speculation lead to ?
> I am not sure to be honest.
Do you enjoy being not sure to be honest ?
> quit
Goodbye. It was nice talking to you!
Connection closed by foreign host.

Type quit to exit Telnet. Congratulations! You’ve just brought Eliza to life. Now let’s connect her to the Agent User Interaction Protocol.

Bridge Eliza with AG-UI

Now that we have our Eliza server running, let’s show how AG-UI can connect to any protocol—in this case, the somewhat arcane Telnet protocol.

Project Setup

Let’s create a new project for our AG-UI bridge:

# Create a directory for your project
mkdir awp-eliza && cd awp-eliza
npm init -y
npm install @ag-ui/client telnetlib
npm install -D typescript ts-node @types/node @types/telnetlib
npx tsc --init --target ES2020 --module CommonJS --strict --esModuleInterop --skipLibCheck --outDir dist --rootDir src
mkdir src

AG-UI Recap

The Agent User Interaction Protocol is a framework that lets you connect agents to clients in a structured, event-driven way. Agents implement the AbstractAgent class from @ag-ui/client and emit events for each piece of content, progress, etc.

ElizaAgent

We’ll create a special agent class, ElizaAgent, which:

  1. Launches a Telnet client to connect to our running Eliza server on localhost:2323.
  2. Sends the user’s last message to Eliza.
  3. Waits for the response, then emits AG-UI events to deliver that text back.

Create a file called src/eliza-agent.ts:

// src/eliza-agent.ts
import {
  AbstractAgent,
  RunAgent,
  RunAgentInput,
  EventType,
  BaseEvent,
  RunStartedEventSchema,
  TextMessageStartEventSchema,
  TextMessageContentEventSchema,
  TextMessageEndEventSchema,
  RunFinishedEventSchema,
} from "@ag-ui/client"
import { Observable } from "rxjs"
import * as telnetlib from "telnetlib"

export class ElizaAgent extends AbstractAgent {
  protected run(input: RunAgentInput): RunAgent {
    // AG-UI calls `run` each time it wants to generate a response.
    return () => {
      // Extract user's latest message

      const lastUserMessage =
        [...input.messages].reverse().find((msg) => msg.role === "user")
          ?.content || "Hello, Eliza"

      return new Observable<BaseEvent>((observer) => {
        // 1) Emit RUN_STARTED
        observer.next(
          RunStartedEventSchema.parse({
            type: EventType.RUN_STARTED,
            threadId: input.threadId,
            runId: input.runId,
          })
        )

        // Create a new telnet client
        const client = telnetlib.createConnection(
          {
            host: "127.0.0.1",
            port: 2323,
          },
          () => {
            console.log("Connected to Eliza telnet server")
          }
        )

        // Wait for initial greeting and prompt
        client.once("data", () => {
          // Once we receive the greeting, send the user message
          client.write(lastUserMessage + "\n")

          // Wait for the response from Eliza
          client.once("data", (data) => {
            // Get the response, removing the prompt
            let response = data.toString().trim()
            // Strip the trailing prompt if present
            if (response.endsWith(">")) {
              response = response.substring(0, response.length - 1).trim()
            }

            // Generate a message ID
            const messageId = Date.now().toString()

            // Emit typical AG-UI message events
            observer.next(
              TextMessageStartEventSchema.parse({
                type: EventType.TEXT_MESSAGE_START,
                messageId,
              })
            )

            observer.next(
              TextMessageContentEventSchema.parse({
                type: EventType.TEXT_MESSAGE_CONTENT,
                messageId,
                delta: response,
              })
            )

            observer.next(
              TextMessageEndEventSchema.parse({
                type: EventType.TEXT_MESSAGE_END,
                messageId,
              })
            )

            // Emit RUN_FINISHED and complete
            observer.next(
              RunFinishedEventSchema.parse({
                type: EventType.RUN_FINISHED,
                threadId: input.threadId,
                runId: input.runId,
              })
            )

            // Close the telnet connection
            client.end()

            observer.complete()
          })
        })

        // Handle errors
        client.on("error", (err) => {
          console.error("Telnet client error:", err)
          observer.error(err)
        })

        // Return a cleanup function
        return () => {
          if (client) {
            client.end()
          }
        }
      })
    }
  }
}

Explanation

  1. Connection We create a TelnetClient and connect to our Eliza Telnet server.

  2. User Message We find the last user message from the AG-UI input. If none exists, we default to “Hello, Eliza”.

  3. Emitting Events

    • RUN_STARTED means the agent started processing the request.
    • TEXT_MESSAGE_START / TEXT_MESSAGE_CONTENT / TEXT_MESSAGE_END together send the agent’s textual response to the AG-UI pipeline.
    • RUN_FINISHED means this run is over.
  4. Round-Trip

    • Write the user message to Eliza over Telnet.
    • Listen for the response from Eliza.
    • Emit that response as AG-UI events.

Putting It All Together

Create a main entry point file src/index.ts that sets up our AG-UI agent:

// src/index.ts
import { ElizaAgent } from "./eliza-agent"

// Create an instance of our agent
const elizaAgent = new ElizaAgent()
elizaAgent.messages = [
  { id: "1", role: "user", content: "I feel anxious about my job." },
]

// Example: Make a request to Eliza through AG-UI
const run = elizaAgent.runAgent({
  runId: "run1",
})

Run the application:

npx ts-node src/index.ts

Bridging AG-UI to Any Protocol

This example demonstrates how to bridge AG-UI with an existing protocol. The key components are:

  1. AG-UI Agent Implementation: Create a class that extends AbstractAgent and implements the run method.
  2. Implement run(): Set up an agent or connect to an existing protocol.
  3. Manage agent input: Send the right input to the agent (in our case, it’s the user’s last message).
  4. Send events: Handle the agent’s response and emit AG-UI events.

You can apply this pattern to connect AG-UI to virtually any protocol or service:

  • HTTP/REST APIs
  • WebSockets
  • MQTT for IoT devices
  • any other protocol

By following this pattern of creating protocol adapters around your AG-UI agents, you can make your AI agents available through any communication channel while maintaining a consistent agent implementation.

Conclusion

Congratulations! You’ve resurrected the first historical chatbot by serving Eliza over Telnet and bridged it to the Agent User Interaction Protocol. This tutorial demonstrates how easy it is to adopt AG-UI with any existing protocol—be it Telnet, WebSocket, custom REST calls, or something entirely else.