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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ Try it yourself in the [Playground](https://www.openui.com/playground): generate
| :--------------------------------------------------------------------------------------------------------- | :----------------------------------------------- | :----------------------------------------------------------------------------------------------------------- |
| [`@openuidev/lang-core`](./packages/lang-core) | Framework-agnostic parsing and prompt generation | Core parser, prompt-generation, runtime-evaluation, and type layer with no React, Vue, or Svelte dependency |
| [`@openuidev/langchain`](./packages/langchain) | LangChain and LangGraph agents | Agent transformer and server helpers that stream OpenUI through AG-UI |
| [`@openuidev/agno`](./packages/agno) | Agno AgentOS agents and teams | AG-UI streaming and AgentOS session adapters for OpenUI chat interfaces |
| [`@openuidev/react-lang`](./packages/react-lang) | React rendering runtimes | Define component libraries, generate prompts, and render streamed OpenUI Lang in React |
| [`@openuidev/react-headless`](./packages/react-headless) | Bring-your-own React chat UI | Headless chat state, streaming adapters, and message format converters |
| [`@openuidev/react-ui`](./packages/react-ui) | Fastest path to a full React chat experience | Prebuilt chat layouts, standalone UI primitives, and two built-in component libraries |
Expand All @@ -117,6 +118,9 @@ npm install @openuidev/lang-core
# LangChain/LangGraph agent and server integration
npm install @openuidev/langchain @langchain/langgraph

# Agno AgentOS integration
npm install @openuidev/agno @openuidev/react-ui

# Vue or Svelte runtime
npm install @openuidev/vue-lang
npm install @openuidev/svelte-lang
Expand Down Expand Up @@ -163,6 +167,7 @@ openui/
│ ├── react-email/ # React Email component library for generated emails
│ ├── lang-core/ # Framework-agnostic parser, prompt, and runtime layer
│ ├── langchain/ # LangChain/LangGraph streaming integration
│ ├── agno/ # Agno AgentOS streaming and session integration
│ ├── vue-lang/ # Vue runtime bindings for OpenUI Lang
│ ├── svelte-lang/ # Svelte runtime bindings for OpenUI Lang
│ ├── browser-bundle/ # Script-tag bundle for CDN / iframe / no-build embeds
Expand Down
153 changes: 153 additions & 0 deletions packages/agno/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# `@openuidev/agno`

Connect an Agno AgentOS to OpenUI without copying transport or persistence glue.

The integration is intentionally complementary:

- **AgentOS handles everything behind the UI:** agents, teams, models, tools,
memory, knowledge, sessions, authorization, execution, and deployment.
- **OpenUI handles the UI:** component instructions, streamed OpenUI Lang,
rendering, interactions, forms, charts, theming, and the chat surface.
- **AG-UI is the boundary** between the two systems.

## Install

```bash
pnpm add @openuidev/agno @openuidev/react-headless @openuidev/react-lang @openuidev/react-ui react
```

Import OpenUI styles once:

```css
@import "@openuidev/react-ui/layered/styles/index.css";
```

## Connect AgentInterface to AgentOS

```tsx
import { agnoOpenUIPromptRenderer, createAgnoLLM, agnoStorage } from "@openuidev/agno";
import { AgentInterface, openuiChatLibrary } from "@openuidev/react-ui";

const llm = createAgnoLLM({
url: "http://localhost:7777/agui",
forwardedProps: { user_id: "demo-user" },
context: [{ description: "openui_client", value: "true" }],
});

const storage = agnoStorage({
baseUrl: "http://localhost:7777",
entityType: "agent",
entityId: "openui-assistant",
userId: "demo-user",
});

export function Chat() {
return (
<AgentInterface
llm={llm}
storage={storage}
componentLibrary={openuiChatLibrary}
artifactRenderers={[agnoOpenUIPromptRenderer]}
/>
);
}
```

For an authenticated AgentOS, pass a scoped bearer token through `token` and
omit `userId`/`forwardedProps.user_id`; AgentOS derives identity from the token.

## What the package owns

- `createAgnoLLM()` adds AgentOS's AG-UI extension containers and configures
the Agno-aware stream adapter.
- `agnoAGUIAdapter()` removes non-chat lifecycle/state events and Agno's empty
tool-parent text envelope while retaining streamed text, tools, and errors.
When assistant text is fenced as `openui`, it removes only that wrapper
incrementally so OpenUI receives the inner language as progressive deltas.
- `agnoStorage()` maps OpenUI threads to AgentOS `/sessions` APIs and reloads
messages from AgentOS `chat_history`. It removes the same optional wrapper
during history conversion, so a reloaded thread follows the live render path.
- The mapping is deliberately 1:1: the OpenUI `thread.id` is the AgentOS
`session_id`, so chat, persistence, inspection, and operational tooling all
address the same conversation without a translation table.
- `agnoHistoryToMessages()` exposes the tolerant history conversion separately
for custom storage implementations.
- `agnoOpenUIPromptRenderer` renders the Agno `prompt_openui` HITL tool.
Submissions become AG-UI tool results that resume the paused AgentOS run.

The package does not run an agent, proxy model keys, or create a second source
of conversation truth.

## AgentOS backend

The backend remains normal Agno Python code. Generate the OpenUI component
prompt from the frontend library, then include it in the agent instructions:

```python
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

@tool(external_execution=True, external_execution_silent=True)
def prompt_openui(ui: str, fallback_markdown: str) -> str:
"""Render a form or choice and wait for its submission."""
return fallback_markdown

def instructions(run_context=None):
dependencies = getattr(run_context, "dependencies", None) or {}
if dependencies.get("openui_client") is True:
return [
openui_system_prompt,
"""For complete visual answers, return exactly one Markdown fence labeled
openui. Put root first and do not add text outside the fence. Use
prompt_openui only when a user action must pause and resume the run;
its ui argument is raw OpenUI Lang without a fence.""",
]
return ["Respond in ordinary text or Markdown. Do not call prompt_openui."]

agent = Agent(
id="openui-assistant",
model=OpenAIResponses(id="gpt-5.5"),
db=SqliteDb(id="openui", db_file="tmp/openui.db"),
tools=[prompt_openui],
instructions=instructions,
add_history_to_context=True,
)

agent_os = AgentOS(agents=[agent], interfaces=[AGUI(agent=agent)])
app = agent_os.get_app()
```

The fence is a single, lossless payload. AgentOS stores and displays it as a
readable Markdown code block; it does not need to render the interface. The
Agno adapter drops only the opening and closing fence as bytes arrive, so the
same payload progressively renders in OpenUI and reloads from session history.

`prompt_openui` is reserved for a real human-in-the-loop boundary: AgentOS
persists a paused run, OpenUI collects the required action and form state, and
the package submits a trailing tool message to resume that same run. A database
is required for AgentOS to reload and resume paused runs. Current AgentOS emits
the prompt tool arguments after the call is complete, so the prompt interface
appears as one tool payload rather than progressively. Its subsequent assistant
answer uses the normal fenced streaming path.

For one agent shared by OpenUI and native AgentOS chat, use a callable Agno
instruction function. The example marks AG-UI requests with the transient
`openui_client` context dependency: those runs receive the generated component
prompt, fenced output rule, and HITL tool rule; native AgentOS runs receive
ordinary Markdown rules and do not call the UI tool.

The local `examples/agno-chat` workspace contains a runnable client, a real
AgentOS server, and a deterministic no-key development harness.

## Current scope

This local implementation supports progressively streamed assistant OpenUI
Lang, paused human tools and resumption, backend tool timelines,
authentication headers, agents/teams, and
AgentOS-backed conversation persistence. The stock AgentOS web app does not render
OpenUI Lang; it can inspect the fenced source while native AgentOS chat stays on
the ordinary text/Markdown path.
58 changes: 58 additions & 0 deletions packages/agno/eslint.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const tseslint = require("@typescript-eslint/eslint-plugin");
const typescript = require("@typescript-eslint/parser");
const prettier = require("eslint-config-prettier");
const unusedImports = require("eslint-plugin-unused-imports");
const eslintPluginPrettier = require("eslint-plugin-prettier");

module.exports = [
{
files: ["**/__tests__/**/*.{ts,tsx}", "**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}"],
languageOptions: {
parser: typescript,
parserOptions: {
project: "./tsconfig.test.json",
sourceType: "module",
},
},
},
{
files: ["**/*.{ts,tsx}"],
ignores: [
"**/__tests__/**/*.{ts,tsx}",
"**/*.test.{ts,tsx}",
"**/*.spec.{ts,tsx}",
"*.config.ts",
],
languageOptions: {
parser: typescript,
parserOptions: {
project: "./tsconfig.json",
sourceType: "module",
},
},
plugins: {
"@typescript-eslint": tseslint,
"unused-imports": unusedImports,
prettier: eslintPluginPrettier,
},
rules: {
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": "off",
"no-undefined": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_",
},
],
"unused-imports/no-unused-imports": "error",
...eslintPluginPrettier.configs.recommended.rules,
},
},
prettier,
];
78 changes: 78 additions & 0 deletions packages/agno/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{
"name": "@openuidev/agno",
"version": "0.0.1",
"description": "OpenUI client and AgentOS session adapters for Agno",
"license": "MIT",
"engines": {
"node": ">=18"
},
"type": "module",
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.cts",
"sideEffects": false,
"files": [
"dist",
"README.md"
],
"exports": {
".": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"scripts": {
"test": "vitest run",
"build": "tsdown",
"watch": "tsdown --watch",
"typecheck": "tsc --noEmit",
"lint:check": "eslint ./src",
"lint:fix": "eslint ./src --fix",
"format:fix": "prettier --write ./src README.md",
"format:check": "prettier --check ./src README.md",
"check:publint": "publint",
"check:attw": "attw --pack .",
"prepare": "pnpm run build",
"prepublishOnly": "pnpm run check:publint && pnpm run check:attw",
"ci": "pnpm run typecheck && pnpm run test && pnpm run lint:check && pnpm run format:check"
},
"keywords": [
"openui",
"agno",
"agentos",
"ag-ui",
"generative-ui",
"streaming"
],
"homepage": "https://openui.com",
"repository": {
"type": "git",
"url": "https://github.com/thesysdev/openui.git",
"directory": "packages/agno"
},
"bugs": {
"url": "https://github.com/thesysdev/openui/issues"
},
"author": "engineering@thesys.dev",
"peerDependencies": {
"@openuidev/react-headless": "workspace:^",
"@openuidev/react-lang": "workspace:^",
"@openuidev/react-ui": "workspace:^",
"react": "catalog:"
},
"devDependencies": {
"@openuidev/react-headless": "workspace:^",
"@openuidev/react-lang": "workspace:^",
"@openuidev/react-ui": "workspace:^",
"@types/react": "catalog:",
"react": "catalog:",
"typescript": "catalog:",
"vitest": "^4.1.0"
}
}
Loading
Loading