agentic_client 0.0.10
agentic_client: ^0.0.10 copied to clipboard
Dart chat client for rendering A2UI widgets over AGUI protocol
agentic_client #
A drop-in Flutter chat widget for AG-UI-compatible agent backends that renders the agent's generative-UI surfaces (A2UI v0.9) inline using a genui catalog of your own Flutter widgets.
[agentic_client example screenshot]
[agentic_client counter screenshot]
Features #
- One widget, full chat surface:
AguiChatowns the transport, theSurfaceController, and the conversation log; just give it abaseUrland aCatalog. - Streamed assistant text rendered as chat bubbles with a thinking indicator while a turn is in flight.
- Generative UI inline via two configurable ingest paths:
- Tool-result envelope (default): A2UI ops in
TOOL_CALL_RESULT.contentunder ana2ui_operationsarray (the CopilotKit convention). - Ghost tool-call args (
uiRenderToolNames): the ops live inTOOL_CALL_ARGSfor a synthetic UI-render tool; the client absorbs the call and synthesizes a tool result back into history. The same path picks up server-side UI patches emitted via LangGraph'smanually_emit_tool_callcustom event, so backends can refresh a surface without re-invoking the LLM.
- Tool-result envelope (default): A2UI ops in
- Shared agent state (
onStateChanged): the client mirrors the agent'sstateviaSTATE_SNAPSHOT/STATE_DELTA(RFC 6902 JSON Patch). The backend is the source of truth: the mirror is read-only on the client and is round-tripped back asSimpleRunAgentInput.stateso stateless agents can resume across runs. WireonStateChangedto observe changes or render debug UIs over them. - UI interaction round-trip: catalog buttons (and any other
UserActionEvent-firing widgets) become an out-of-band action signal. The transport packs the action intoforwarded_props.pending_actionso your agent can handle it via a pre-LLM node or middleware without treating the click as a chat message. - Inline agent-event steps (
showAgentEvents: true): show small italic step rows like "Calling render_ui_widget…" / "Rendering 4 components" / "Updating data" as the agent works, instead of a single loader spinner.
Getting started #
Prerequisites:
- Flutter
>=3.35.7(Dart SDK^3.12.2). - An AG-UI-compatible agent endpoint reachable over HTTP. The widget POSTs each
turn to
"$baseUrl/". - A2UI ops from the agent must travel inside
TOOL_CALL_RESULT.contentunder thea2ui_operationsenvelope (the CopilotKit convention).
Add the package to your app's pubspec.yaml:
dependencies:
agentic_client: ^0.0.1
genui: ^0.9.2
Usage #
Build a Catalog of the widgets your agent is allowed to render,
then drop AguiChat into any Scaffold:
import 'package:agentic_client/agentic_client.dart';
import 'package:flutter/material.dart';
import 'package:genui/genui.dart';
class ChatScreen extends StatelessWidget {
const ChatScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Chat')),
body: AguiChat(
baseUrl: 'http://localhost:8123',
catalog: Catalog(
[
BasicCatalogItems.text,
BasicCatalogItems.column,
BasicCatalogItems.row,
// …plus your own CatalogItems
],
catalogId: 'my-app.catalog',
),
catalogDescription: '... optional A2UI prompt context ...',
hintText: 'Ask me anything…',
// Optional ghost tool-call path on top of the always-on
// tool-result envelope.
uiRenderToolNames: const {'render_ui_widget', 'emit_ui_update'},
// Observe shared state. Fires on STATE_SNAPSHOT and STATE_DELTA.
// The backend is the source of truth; the map is a fresh copy on
// every call.
onStateChanged: (state) {
// e.g. setState(() => _agentState = state);
},
// Inline step rows ("Calling …", "Rendering 4 components",
// "Updating data") instead of a single loader spinner.
showAgentEvents: true,
),
);
}
}
catalogDescription is shipped to the agent on every request via the AG-UI
context field. Use it to teach the model the wire format and the props each
of your components accepts. For fixed-schema backends (where the agent's tool
already knows the catalog), leave it null.
Shared state and UI actions #
When you wire onStateChanged, the client mirrors the agent's shared state
via STATE_SNAPSHOT / STATE_DELTA (RFC 6902 JSON Patch) and echoes the
mirror back as SimpleRunAgentInput.state on every turn. The mirror is
read-only on the client; the backend owns all writes.
When a user taps an A2UI Button (or any widget that fires a
UserActionEvent), the transport packs the action into
forwarded_props.pending_action instead of treating it as a chat message.
A backend pre-LLM hook can read state["pending_action"], mutate state,
and either short-circuit the graph (silent) or inject a synthetic
HumanMessage (chatty).
For backends that need to refresh a surface without invoking the LLM (for
example, after a silent state mutation), dispatch a LangGraph custom event
named manually_emit_tool_call with args set to a JSON-encoded
{"a2ui_operations": [...]} envelope and the tool name registered in
uiRenderToolNames. The client unwraps the envelope and applies the ops
to the surface, so components with {"path": "/..."} bindings update in
place.
A complete runnable app, including custom ProductCard, WeatherTile and
Stat catalog items, lives in /example. Run it against any
AG-UI agent with:
cd example
flutter run --dart-define=AGENT_URL=http://localhost:8123