Skip to content
Open
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
26 changes: 26 additions & 0 deletions docs/content/docs/agent/reference/agentinterface-props.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The only required prop is `llm`. Everything else is optional and falls back to a
| `agentName` | `string` | No | none |
| `starters` | `ConversationStarterProps[]` | No | none |
| `starterVariant` | `"short" \| "long"` | No | — |
| `getThreadMenuActions` | `GetThreadMenuActions` | No | none |
| `path` | `string` | No | — (uncontrolled) |
| `defaultPath` | `string` | No | thread view (`undefined`) |
| `onNavigate` | `(next: string \| undefined) => void` | No | — (uncontrolled) |
Expand Down Expand Up @@ -122,6 +123,31 @@ Set `disableThemeProvider` to `true` when `AgentInterface` is mounted inside an

These feed the default `SidebarHeader` and `MobileHeader`. To go further, replace those slots. See [Sidebar](/docs/agent/customize/sidebar).

## Thread menu actions

Use `getThreadMenuActions` to add context-aware links or callbacks to each thread's overflow menu. The callback receives `{ id, title, isSelected, isRunning }`, where `isRunning` is true only for the selected thread while its response is streaming.

```tsx
<AgentInterface
llm={llm}
getThreadMenuActions={({ id, isRunning }) =>
isRunning
? []
: [
{
id: "inspect-session",
label: "Inspect session",
href: `/sessions/${encodeURIComponent(id)}`,
target: "_blank",
rel: "noopener noreferrer",
},
]
}
/>;
```

Each action must have a stable `id` and `label`, plus either `href` for a real anchor or `onSelect` for an in-app callback. An optional `icon` renders before the label.

## Starters

Conversation starters are the suggested prompts shown on the welcome screen and in the composer.
Expand Down
1 change: 1 addition & 0 deletions docs/content/docs/api-reference/react-ui.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const llm: ChatLLM = {
- `labels?: AgentInterfaceLabels`
- `starters?: ConversationStarterProps[]`
- `starterVariant?: ConversationStarterVariant`
- `getThreadMenuActions?: GetThreadMenuActions` — adds link or callback actions to each thread menu
- `scrollVariant?: ScrollVariant`
- `scrollOnLoad?: boolean`
- Theme wrapper props:
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Each example has one primary home based on the integration seam it is intended t

| Example | Demonstrates |
| ------------------------------------------------- | ----------------------------------------------------------------------- |
| [Agno](./agent-frameworks/agno) | An AgentOS agent streamed into OpenUI with shared session persistence |
| [Google ADK](./agent-frameworks/google-adk) | A Google ADK TypeScript agent streaming OpenUI Lang to a Next.js client |
| [LangChain](./agent-frameworks/langchain) | LangGraph/DeepAgents integration through the OpenUI LangChain adapter |
| [Mastra](./agent-frameworks/mastra) | A Mastra agent connected to OpenUI through AG-UI |
Expand Down
8 changes: 8 additions & 0 deletions examples/agent-frameworks/agno/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules
.venv
__pycache__
dist
.env
.env.local
src/generated
tmp
116 changes: 116 additions & 0 deletions examples/agent-frameworks/agno/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Agno × OpenUI

This example demonstrates the complementary boundary:

```text
AgentOS: agents · teams · tools · memory · knowledge · sessions · auth · execution
AG-UI
OpenUI: component contract · streaming parser · renderer · interactions · chat UI
```

The browser uses `@openuidev/agno` for both channels expected by
`AgentInterface`:

- `createAgnoLLM()` streams an AgentOS AG-UI run and marks it as an OpenUI client.
- `agnoStorage()` stores the sidebar and message history in AgentOS sessions.
- `agnoAGUIAdapter()` incrementally unwraps fenced assistant OpenUI Lang while
AgentOS retains the same payload as readable Markdown source.
- `agnoOpenUIPromptRenderer` renders the true paused `prompt_openui` HITL call.

## Run without a model key

The Vite development server includes a deterministic AgentOS-compatible
harness. It exercises session CRUD, Agno's empty tool-parent envelope, a backend
tool result, multi-delta assistant-text streaming, follow-ups, history reload,
and a true paused/resumed `prompt_openui` form.

```bash
pnpm dev
```

Open `http://127.0.0.1:4173` and try both starters.

## Run with AgentOS

Configure the required model credential outside the repository, then:

```bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
pnpm generate:prompt
python server.py
```

In another terminal, point the Vite proxy at AgentOS:

```bash
AGNO_API_URL=http://127.0.0.1:7777 pnpm dev
```

The React application does not change.

The **Open in AgentOS** action defaults to the hosted AgentOS session page. To
point it at another AgentOS web interface, provide an absolute URL template with
the `{session_id}` placeholder:

```bash
VITE_AGENT_OS_SESSION_URL_TEMPLATE='https://os.agno.com/sessions/{session_id}' pnpm dev
```

This configures the operational web-interface link, not the AgentOS backend API.
The example adds the current session-list query parameters after replacing the
placeholder.

The proxy is development plumbing only: the browser calls same-origin `/agui`
and `/sessions`, while Vite forwards those paths to port 7777 and avoids local
CORS configuration. A production app can use its normal reverse proxy or pass
an already same-origin AgentOS endpoint.

The Python server is deliberately ordinary Agno code: it owns the model, tool,
database, history, and AG-UI interface. The component library and all rendering
remain in the OpenUI frontend.

The same agent remains usable from native AgentOS chat. OpenUI requests carry a
transient `openui_client` context dependency and receive the generated component
prompt. Requests without that marker are instructed to answer in normal
text/Markdown and not call the UI tool.

Complete visual answers are one fenced assistant-text payload. AgentOS stores
and shows the exact OpenUI Lang inside a Markdown code block. AG-UI streams that
text as `TEXT_MESSAGE_CONTENT` events; `@openuidev/agno` removes only the fence
and OpenUI renders the inner language as it arrives. The same normalization is
applied when the session is reloaded.

`prompt_openui` is used only when a form or choice must pause execution. The
first request persists the pending run, the OpenUI form submission is sent back
as a tool result, and AgentOS resumes the same run and `session_id`. Current
AgentOS sends the completed prompt tool arguments as one event, so the form
itself appears after the tool call closes; the resumed assistant answer streams
normally.

## Key files

- `server.py` configures the Agno agent, tools, session database, and AG-UI
interface.
- `src/App.tsx` connects Agent Interface to AgentOS streaming and storage.
- `src/library.ts` defines the OpenUI component library used by the model.
- `src/mock-agentos.ts` provides the credential-free local verification
harness.

## Extend the example

Add backend capabilities as ordinary Agno tools in `server.py`. Add or replace
frontend components in `src/library.ts`, then regenerate the prompt before
running the real AgentOS server. Production applications can also replace the
development proxy and hosted session URL template with their own endpoints.

## Verify

From this directory, run the credential-free verification contract:

```bash
pnpm verify
```
13 changes: 13 additions & 0 deletions examples/agent-frameworks/agno/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Agno AgentOS with OpenUI generative UI" />
<title>Agno × OpenUI</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
33 changes: 33 additions & 0 deletions examples/agent-frameworks/agno/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@openuidev/example-agno",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"generate:prompt": "pnpm --filter @openuidev/cli build && pnpm exec openui generate src/library.ts --prompt-options promptOptions --out src/generated/system-prompt.txt",
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
"verify": "pnpm typecheck && pnpm build"
},
"dependencies": {
"@openuidev/agno": "workspace:*",
"@openuidev/react-headless": "workspace:*",
"@openuidev/react-lang": "workspace:*",
"@openuidev/react-ui": "workspace:*",
"lucide-react": "^0.562.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"zod": "^4.0.0",
"zustand": "catalog:"
},
"devDependencies": {
"@openuidev/cli": "workspace:*",
"@types/node": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"typescript": "catalog:",
"vite": "^6.0.0"
}
}
1 change: 1 addition & 0 deletions examples/agent-frameworks/agno/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
agno[agui,openai,os,sqlite]>=2.9.0
91 changes: 91 additions & 0 deletions examples/agent-frameworks/agno/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""Serve an Agno Agent through AgentOS while OpenUI owns the browser UI."""

from os import getenv
from pathlib import Path

from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIResponses
from agno.os import AgentOS
from agno.os.interfaces.agui import AGUI
from agno.tools import tool

EXAMPLE_ROOT = Path(__file__).resolve().parent
OPENUI_PROMPT_PATH = EXAMPLE_ROOT / "src" / "generated" / "system-prompt.txt"

if not OPENUI_PROMPT_PATH.is_file():
raise RuntimeError(
"OpenUI system prompt is missing. Run `pnpm generate:prompt` in "
"examples/agent-frameworks/agno."
)


@tool
def get_quarterly_revenue() -> dict:
"""Return quarterly revenue in thousands of US dollars."""
return {
"currency": "USD",
"unit": "thousands",
"quarters": [
{"quarter": "Q1", "revenue": 120},
{"quarter": "Q2", "revenue": 180},
{"quarter": "Q3", "revenue": 150},
{"quarter": "Q4", "revenue": 240},
],
}


@tool(external_execution=True, external_execution_silent=True)
def prompt_openui(ui: str, fallback_markdown: str) -> str:
"""Render an OpenUI form or choice and wait for the user to submit it."""
return fallback_markdown


def agent_instructions(run_context=None) -> list[str]:
"""Stream rich OpenUI to the AG-UI client and Markdown elsewhere."""
dependencies = getattr(run_context, "dependencies", None) or {}
if dependencies.get("openui_client") is True:
return [
"Use get_quarterly_revenue for stored revenue questions.",
OPENUI_PROMPT_PATH.read_text(encoding="utf-8"),
(
"For every complete visual answer, stream the OpenUI Lang as the assistant text "
"inside exactly one Markdown code fence labeled openui. The first bytes must be "
"```openui followed by a newline, and the final bytes must be a newline followed "
"by ```. Put root first inside the fence. Do not add prose before or after it. "
"This wrapper rule intentionally overrides any earlier instruction that forbids "
"Markdown fences. Use prompt_openui only when the user "
"must submit a form or choice before the run can continue. For prompt_openui, set "
"ui to raw OpenUI Lang without fences and fallback_markdown to a concise Markdown "
"description for AgentOS. After the prompt resumes, return the next complete visual "
"answer as fenced assistant text."
),
]

return [
"Use get_quarterly_revenue for stored revenue questions.",
"Respond in normal text or Markdown. Do not call prompt_openui.",
]


agent = Agent(
id="openui-assistant",
name="Agno × OpenUI Assistant",
model=OpenAIResponses(id=getenv("OPENAI_MODEL", "gpt-5.5")),
db=SqliteDb(id="agno-openui", db_file="tmp/agno_openui.db"),
tools=[get_quarterly_revenue, prompt_openui],
instructions=agent_instructions,
add_history_to_context=True,
num_history_runs=10,
)

agent_os = AgentOS(
id="agno-openui-os",
description="AgentOS owns the agent runtime; OpenUI owns the user interface.",
agents=[agent],
interfaces=[AGUI(agent=agent)],
)
app = agent_os.get_app()

if __name__ == "__main__":
agent_os.serve(app=app)
Loading
Loading