reflex-agentkit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. reflex_agentkit/__init__.py +68 -0
  2. reflex_agentkit/adapters/__init__.py +1 -0
  3. reflex_agentkit/adapters/_require.py +27 -0
  4. reflex_agentkit/adapters/langgraph.py +277 -0
  5. reflex_agentkit/adapters/pydantic_ai.py +93 -0
  6. reflex_agentkit/components/__init__.py +21 -0
  7. reflex_agentkit/components/approval.py +123 -0
  8. reflex_agentkit/components/chat.py +42 -0
  9. reflex_agentkit/components/composer.py +44 -0
  10. reflex_agentkit/components/messages.py +104 -0
  11. reflex_agentkit/components/shared_state.py +32 -0
  12. reflex_agentkit/components/status.py +41 -0
  13. reflex_agentkit/components/tools.py +97 -0
  14. reflex_agentkit/core/__init__.py +56 -0
  15. reflex_agentkit/core/_log.py +47 -0
  16. reflex_agentkit/core/_patch.py +46 -0
  17. reflex_agentkit/core/clients/__init__.py +14 -0
  18. reflex_agentkit/core/clients/base.py +71 -0
  19. reflex_agentkit/core/clients/http.py +147 -0
  20. reflex_agentkit/core/clients/in_process.py +67 -0
  21. reflex_agentkit/core/clients/sse.py +75 -0
  22. reflex_agentkit/core/commands.py +589 -0
  23. reflex_agentkit/core/conversation.py +93 -0
  24. reflex_agentkit/core/errors.py +74 -0
  25. reflex_agentkit/core/models.py +243 -0
  26. reflex_agentkit/core/reducer.py +1078 -0
  27. reflex_agentkit/core/run_input.py +122 -0
  28. reflex_agentkit/core/tools.py +204 -0
  29. reflex_agentkit/py.typed +0 -0
  30. reflex_agentkit/state/__init__.py +5 -0
  31. reflex_agentkit/state/_driver.py +520 -0
  32. reflex_agentkit/state/_projection.py +120 -0
  33. reflex_agentkit/state/_registry.py +46 -0
  34. reflex_agentkit/state/agent_state.py +496 -0
  35. reflex_agentkit-0.1.0.dist-info/METADATA +457 -0
  36. reflex_agentkit-0.1.0.dist-info/RECORD +38 -0
  37. reflex_agentkit-0.1.0.dist-info/WHEEL +4 -0
  38. reflex_agentkit-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,457 @@
1
+ Metadata-Version: 2.5
2
+ Name: reflex-agentkit
3
+ Version: 0.1.0
4
+ Summary: The production UI layer for Python AI agents, built on Reflex and the AG-UI protocol.
5
+ Project-URL: Homepage, https://github.com/ecrespo/reflex-agentkit
6
+ Project-URL: Repository, https://github.com/ecrespo/reflex-agentkit
7
+ Project-URL: Issues, https://github.com/ecrespo/reflex-agentkit/issues
8
+ Project-URL: Changelog, https://github.com/ecrespo/reflex-agentkit/blob/main/CHANGELOG.md
9
+ Author-email: Ernesto Crespo <ecrespo@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: ag-ui,agents,chat,langgraph,llm,pydantic-ai,reflex
13
+ Classifier: Development Status :: 2 - Pre-Alpha
14
+ Classifier: Framework :: AsyncIO
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: <3.14,>=3.11
24
+ Requires-Dist: ag-ui-protocol<2,>=1.0.0
25
+ Requires-Dist: httpx<1,>=0.28
26
+ Requires-Dist: jsonpatch<2,>=1.33
27
+ Requires-Dist: pydantic<3,>=2.11.2
28
+ Requires-Dist: reflex<0.11,>=0.10.1
29
+ Provides-Extra: all
30
+ Requires-Dist: ag-ui-langgraph[fastapi]<0.1,>=0.0.47; extra == 'all'
31
+ Requires-Dist: langgraph<2,>=1.2; extra == 'all'
32
+ Requires-Dist: pydantic-ai-slim[ag-ui,anthropic,openai]<3,>=2.64; extra == 'all'
33
+ Provides-Extra: langgraph
34
+ Requires-Dist: ag-ui-langgraph[fastapi]<0.1,>=0.0.47; extra == 'langgraph'
35
+ Requires-Dist: langgraph<2,>=1.2; extra == 'langgraph'
36
+ Provides-Extra: pydantic-ai
37
+ Requires-Dist: pydantic-ai-slim[ag-ui,anthropic,openai]<3,>=2.64; extra == 'pydantic-ai'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # reflex-agentkit
41
+
42
+ Build and serve AI agents in pure Python with Reflex. AG-UI protocol client, streaming
43
+ chat, tool-call cards and human-in-the-loop approval for LangGraph, PydanticAI and any
44
+ AG-UI-compatible agent. The production UI layer your agent was missing — no TypeScript,
45
+ no Streamlit.
46
+
47
+ > **Status: `0.1.0`, the first release.** Everything in v0.1 is implemented and tested: the pure core,
48
+ > the agent clients, the Reflex state engine (`AgentState`), the chat components, both
49
+ > adapters (PydanticAI and LangGraph) and the AG-UI Dojo examples.
50
+ > `pip install "reflex-agentkit[pydantic-ai]"` or `uv add "reflex-agentkit[pydantic-ai]"`. Documentation: [`docs/`](docs/) (`uv run mkdocs serve`).
51
+
52
+ ## Why
53
+
54
+ A Python developer with a working agent usually has two options for its UI: Streamlit,
55
+ which re-runs the whole script on every interaction and has to work around streaming tool
56
+ calls, mid-run approvals and cancellation; or a Next.js frontend, which adds a second
57
+ language, build and deployment. reflex-agentkit is the middle: Reflex already holds the
58
+ state on the server, keeps a WebSocket to every browser and runs background tasks, and
59
+ this library adds the agent layer on top, speaking the AG-UI protocol that PydanticAI and
60
+ LangGraph already emit.
61
+
62
+ ## Quickstart
63
+
64
+ A streaming chat with a PydanticAI travel agent, one tool call shown as a card and one
65
+ tool that waits for your approval. This is the whole app
66
+ ([`examples/quickstart/`](examples/quickstart/)):
67
+
68
+ <!-- quickstart:start -->
69
+ ```python
70
+ from typing import ClassVar
71
+
72
+ import reflex as rx
73
+ from pydantic_ai import Agent
74
+
75
+ from reflex_agentkit import AgentState, agent_chat
76
+ from reflex_agentkit.adapters.pydantic_ai import PydanticAIAgent
77
+
78
+ travel_agent = Agent(
79
+ "anthropic:claude-sonnet-5-5",
80
+ instructions="You are a helpful travel assistant.",
81
+ )
82
+
83
+
84
+ @travel_agent.tool_plain
85
+ def get_weather(city: str) -> str:
86
+ """Get the current weather for a city."""
87
+ return f"It is sunny and 22 °C in {city}."
88
+
89
+
90
+ @travel_agent.tool_plain(requires_approval=True)
91
+ def book_hotel(city: str, nights: int) -> str:
92
+ """Book a hotel room. Requires human approval."""
93
+ return f"Booked {nights} nights in {city}."
94
+
95
+
96
+ class ChatState(AgentState, rx.State):
97
+ agent: ClassVar = PydanticAIAgent(travel_agent)
98
+
99
+
100
+ def index() -> rx.Component:
101
+ return agent_chat(ChatState)
102
+
103
+
104
+ app = rx.App()
105
+ app.add_page(index)
106
+ ```
107
+ <!-- quickstart:end -->
108
+
109
+ Run it from a clone of this repo:
110
+
111
+ ```bash
112
+ uv sync --all-extras
113
+ cd examples/quickstart
114
+ ANTHROPIC_API_KEY=sk-ant-... uv run --package quickstart reflex run
115
+ ```
116
+
117
+ Ask it to plan a trip: it shows the `get_weather` call and opens the approval dialog
118
+ before `book_hotel` runs. The agentkit components are built on Radix Themes, so the
119
+ example's `rxconfig.py` adds `rx.plugins.RadixThemesPlugin()`.
120
+
121
+ To use a local model instead of Claude, swap the model line for an Ollama model through
122
+ PydanticAI's Ollama provider, for example `Agent("ollama:qwen3", ...)` with
123
+ `OLLAMA_BASE_URL=http://localhost:11434/v1`.
124
+
125
+ The components are in `reflex_agentkit.components`: `agent_chat` composes
126
+ `message_list`, `tool_call_card`, `composer`, `run_status_bar`, `approval_dialog` and,
127
+ with `show_shared_state=True`, `shared_state_panel`. Custom renderers for a tool
128
+ (`tool_renderers={"get_weather": ...}`) or an interrupt reason
129
+ (`interrupt_renderers={...}`) replace the default card body or form. Every control has
130
+ an accessible label.
131
+
132
+ ## Features
133
+
134
+ | Feature | How |
135
+ |---|---|
136
+ | Streaming replies | Markdown streamed token by token, batched to at most one push per 50 ms |
137
+ | Tool calls | A card per call with arguments, status and result; custom renderers per tool |
138
+ | Approvals | Approve, edit the arguments, or reject a tool call before it runs |
139
+ | Questions from the agent | AG-UI standard interrupts, answered with a replaceable form |
140
+ | Shared state | The agent's state mirrored in `shared_state`, optionally validated by a Pydantic model, editable by the app |
141
+ | Client tools | Tools the agent calls that run in the Reflex app, e.g. to draw a chart |
142
+ | Real cancellation | Stop closes the agent's stream within a second, also across workers |
143
+ | Any AG-UI agent | In process (PydanticAI, LangGraph adapters) or over HTTP (`HttpAGUIClient`) |
144
+ | Safe by default | Agent, clients and keys never become browser state; every handler validates its input |
145
+
146
+ ## How it compares
147
+
148
+ | | reflex-agentkit | Streamlit | CopilotKit | agentstage |
149
+ |---|---|---|---|---|
150
+ | UI written in | Python (Reflex) | Python | React / TypeScript | Python |
151
+ | Agent protocol | AG-UI | none (your own wiring) | AG-UI | its own JSON API over SSE |
152
+ | Agent frameworks | PydanticAI, LangGraph, any AG-UI server | any, by hand | many, through AG-UI | LangChain, LangGraph |
153
+ | Streaming and tool calls | built in | by hand, script reruns | built in | built in |
154
+ | Approvals and questions | AG-UI interrupts, edit before approving | by hand | built in | LangGraph interrupts |
155
+ | Shared state, client tools | built in | by hand | built in | — |
156
+ | Your own UI | any Reflex component | any Streamlit widget | any React component | configured app |
157
+
158
+ *agentstage (`agentstage-ui` on PyPI, 0.1.2, September 2026) per its README.*
159
+
160
+ ## Documentation
161
+
162
+ The MkDocs site in [`docs/`](docs/) covers getting started (uv and pip), the concepts,
163
+ guides (your own UI, human in the loop, sidecar deployment, local models with Ollama), the
164
+ API reference, the security model and troubleshooting. Preview it with
165
+ `uv run mkdocs serve`.
166
+
167
+ ## Progress
168
+
169
+ Work follows the approved spec chain in [`specs/`](specs/) and the task list in
170
+ [`specs/tasks/agentkit-tasks.md`](specs/tasks/agentkit-tasks.md).
171
+
172
+ | Phase | Scope | Status |
173
+ |---|---|---|
174
+ | WP0 — Foundation | Packaging (uv, hatchling), lint, types, tests, CI | ✅ done |
175
+ | WP1 — Pure core | Models, reducer, commands, client tools, `RunInputBuilder`, property tests | ✅ done |
176
+ | WP2 — Clients | `AgentClient`/`AGUIAgent` protocols, SSE parser, `HttpAGUIClient`, `InProcessAGUIClient` | ✅ done |
177
+ | WP3 — Reflex state engine | `AgentState` mixin, streaming driver, cancellation, client-tool rounds | ✅ done |
178
+ | WP4 — PydanticAI adapter | `PydanticAIAgent`: a PydanticAI agent as an `AGUIAgent`, tool approvals, client tools | ✅ done |
179
+ | WP5 — Components and quickstart | `agent_chat` and its parts, quickstart example, release workflow | ✅ done |
180
+ | WP6 — LangGraph adapter | `LangGraphAGUIAgent`, `request_tool_approval`, legacy interrupts | ✅ done |
181
+ | WP7 — Dojo examples | The five AG-UI Dojo demos for PydanticAI and LangGraph | ✅ done |
182
+ | WP8 — Hardening, docs, release | Security review, benchmarks, documentation site, `0.1.0` on PyPI | ✅ done |
183
+
184
+ ## What works today: `reflex_agentkit.core`
185
+
186
+ The core never imports `reflex`. AG-UI is its only event model, and a pure, total reducer
187
+ is the only way agent events change the conversation state.
188
+
189
+ - **`reduce(state, event)`**: handles all 31 AG-UI 1.0 event types (lifecycle and
190
+ `RUN_FINISHED` outcomes, text, tool calls, shared state via JSON Patch, messages
191
+ snapshots, reasoning, custom events). It never raises: problems become diagnostics.
192
+ - **Commands** (`reflex_agentkit.core.commands`): `begin_run`, `apply_approval`
193
+ (optionally with edited arguments), `apply_rejection`, `apply_interrupt_response`,
194
+ `apply_cancel`, `apply_client_tool_result`, `new_thread`, `resume_entries`.
195
+ Human-in-the-loop follows AG-UI standard interrupts and the PydanticAI approval payload.
196
+ - **Client tools**: `@client_tool` builds the JSON Schema from type hints. `invoke()`
197
+ runs sync handlers in a worker thread under a timeout and returns failures as tool
198
+ results.
199
+ - **`RunInputBuilder`**: turns the conversation into an AG-UI `RunAgentInput` (full
200
+ history, encrypted reasoning, edited tool args, shared state, tools, resume entries).
201
+ - **Agent clients**: `HttpAGUIClient` talks to any AG-UI server over HTTP + SSE.
202
+ `InProcessAGUIClient` runs an agent object (anything with
203
+ `run(run_input) -> AsyncIterator[event]`) in the same event loop. Both yield validated
204
+ events, skip invalid ones with a log record, raise typed `AgentHTTPError` /
205
+ `AgentStreamError` on transport failures, and close the upstream when the consumer is
206
+ cancelled. Headers such as API keys are never logged or put in error messages.
207
+
208
+ ```python
209
+ from reflex_agentkit.core import ConversationState, RunInputBuilder, client_tool
210
+ from reflex_agentkit.core.commands import apply_approval, begin_run
211
+ from reflex_agentkit.core.models import ReducerConfig
212
+ from reflex_agentkit.core.reducer import parse_event, reduce
213
+
214
+
215
+ @client_tool
216
+ def pick_date(day: int) -> str:
217
+ """Let the user pick a day of the month."""
218
+ return f"2026-05-{day:02d}"
219
+
220
+
221
+ config = ReducerConfig(client_tool_names=frozenset({"pick_date"}))
222
+ conv = begin_run(ConversationState.initial(config), config=config, user_text="book it")
223
+ run_input = RunInputBuilder(client_tools=(pick_date,)).build(conv) # send to the agent
224
+
225
+ for raw in [ # AG-UI events as they arrive on the wire
226
+ {"type": "RUN_STARTED", "threadId": conv.thread_id, "runId": conv.run_id},
227
+ {"type": "TOOL_CALL_START", "toolCallId": "c1", "toolCallName": "book_hotel"},
228
+ {"type": "TOOL_CALL_ARGS", "toolCallId": "c1", "delta": '{"city": "Lisbon"}'},
229
+ {"type": "TOOL_CALL_END", "toolCallId": "c1"},
230
+ {"type": "RUN_FINISHED", "threadId": conv.thread_id, "runId": conv.run_id,
231
+ "outcome": {"type": "interrupt",
232
+ "interrupts": [{"id": "int-c1", "reason": "tool_call", "toolCallId": "c1"}]}},
233
+ ]:
234
+ event = parse_event(raw)
235
+ conv = reduce(conv, event)
236
+
237
+ print(conv.run_status) # awaiting_input
238
+ conv = apply_approval(conv, "c1", {"city": "Porto"})
239
+ conv = begin_run(conv, config=config, resume=True)
240
+ print(RunInputBuilder().build(conv).resume) # [ResumeEntry(..., payload={'approved': True, 'editedArgs': {...}})]
241
+ ```
242
+
243
+ Streaming a run from an AG-UI server:
244
+
245
+ ```python
246
+ import asyncio
247
+ import os
248
+
249
+ from reflex_agentkit import HttpAGUIClient
250
+ from reflex_agentkit.core import ConversationState, RunInputBuilder
251
+ from reflex_agentkit.core.commands import begin_run
252
+ from reflex_agentkit.core.models import ReducerConfig
253
+ from reflex_agentkit.core.reducer import reduce
254
+
255
+
256
+ async def main() -> None:
257
+ client = HttpAGUIClient(
258
+ "http://localhost:8000/agent",
259
+ headers=lambda: {"Authorization": f"Bearer {os.environ['AGENT_TOKEN']}"},
260
+ )
261
+ config = ReducerConfig()
262
+ conv = begin_run(ConversationState.initial(config), config=config, user_text="Hello!")
263
+ try:
264
+ async for event in client.stream(RunInputBuilder().build(conv)):
265
+ conv = reduce(conv, event)
266
+ finally:
267
+ await client.aclose()
268
+ print(conv.run_status, conv.messages[-1].content)
269
+
270
+
271
+ asyncio.run(main())
272
+ ```
273
+
274
+ ## `AgentState` with your own UI
275
+
276
+ `AgentState` is a Reflex state mixin that holds a conversation with an AG-UI agent. Its
277
+ configuration is class-level (`ClassVar`), so the agent, its client and any API keys
278
+ never become state vars sent to the browser. `agent_chat` is optional: this example
279
+ builds its own UI from plain Reflex components:
280
+
281
+ ```python
282
+ from typing import ClassVar
283
+
284
+ import reflex as rx
285
+
286
+ from reflex_agentkit import AgentState, HttpAGUIClient
287
+
288
+
289
+ class ChatState(AgentState, rx.State):
290
+ agent: ClassVar = HttpAGUIClient("http://localhost:8000/agent")
291
+ draft: str = ""
292
+
293
+ @rx.event
294
+ def set_draft(self, value: str) -> None:
295
+ self.draft = value
296
+
297
+ @rx.event
298
+ def submit(self):
299
+ text, self.draft = self.draft, ""
300
+ return ChatState.send_message(text)
301
+
302
+
303
+ def index() -> rx.Component:
304
+ return rx.vstack(
305
+ rx.foreach(ChatState.messages, lambda m: rx.text(m.role, ": ", m.content)),
306
+ rx.cond(ChatState.error != "", rx.callout(ChatState.error, color_scheme="red")),
307
+ rx.hstack(
308
+ rx.input(value=ChatState.draft, on_change=ChatState.set_draft),
309
+ rx.button("Send", on_click=ChatState.submit, disabled=~ChatState.can_send),
310
+ rx.button("Stop", on_click=ChatState.cancel_run, disabled=~ChatState.is_running),
311
+ ),
312
+ )
313
+
314
+
315
+ app = rx.App()
316
+ app.add_page(index)
317
+ ```
318
+
319
+ - **Streaming**: the reply streams in a background task; state pushes are batched (at most
320
+ one per `flush_interval_ms`, default 50 ms).
321
+ - **Handlers**: `send_message`, `cancel_run` (stops within 1 s, also when another backend
322
+ worker handles the click), `approve_tool` / `reject_tool` (with edited arguments),
323
+ `respond_to_interrupt`, `new_thread`, `set_shared_state` / `patch_shared_state`,
324
+ `retry_run`. Calls that are not allowed in the current status are no-ops, so they are
325
+ safe to call from the browser at any time.
326
+ - **Client tools**: `client_tools: ClassVar = (my_tool,)` runs `@client_tool` handlers on
327
+ the server and starts the follow-up run, with optional approval
328
+ (`@client_tool(requires_approval=True)`).
329
+ - **Hooks**: override `create_client` (for example, per-user auth headers),
330
+ `prepare_run_input`, `on_custom_event` or `on_run_end`.
331
+
332
+ ## What works today: the PydanticAI adapter
333
+
334
+ Install the extra (`reflex-agentkit[pydantic-ai]`) and wrap the agent. Tools declared with
335
+ `requires_approval=True` pause the run for `approve_tool` / `reject_tool` with no extra
336
+ configuration; edited arguments reach the tool.
337
+
338
+ ```python
339
+ from typing import ClassVar
340
+
341
+ import reflex as rx
342
+ from pydantic_ai import Agent
343
+
344
+ from reflex_agentkit import AgentState
345
+ from reflex_agentkit.adapters.pydantic_ai import PydanticAIAgent
346
+
347
+ agent = Agent("anthropic:claude-sonnet-5-5", instructions="You book hotels.")
348
+
349
+
350
+ @agent.tool_plain(requires_approval=True)
351
+ def book_hotel(city: str) -> str:
352
+ """Book a hotel in a city."""
353
+ return f"Booked a hotel in {city}."
354
+
355
+
356
+ class ChatState(AgentState, rx.State):
357
+ agent: ClassVar = PydanticAIAgent(agent)
358
+ ```
359
+
360
+ ## What works today: the LangGraph adapter
361
+
362
+ Install the extra (`reflex-agentkit[langgraph]`) and wrap a compiled graph that has a
363
+ checkpointer. `interrupt(value)` in the graph shows up as `pending_interrupt` and is
364
+ answered with `respond_to_interrupt`. `request_tool_approval` pauses for a tool call and
365
+ shows the approval dialog; it returns the decision, with edited arguments when the user
366
+ changed them. In this sketch, `call_model` stands for your own chat-model node:
367
+
368
+ ```python
369
+ from typing import Any, ClassVar
370
+
371
+ import reflex as rx
372
+ from langchain_core.messages import AIMessage, ToolMessage
373
+ from langgraph.checkpoint.memory import InMemorySaver
374
+ from langgraph.graph import END, START, MessagesState, StateGraph
375
+
376
+ from reflex_agentkit import AgentState, agent_chat
377
+ from reflex_agentkit.adapters.langgraph import LangGraphAGUIAgent, request_tool_approval
378
+
379
+
380
+ def tools(state: MessagesState) -> dict[str, Any]:
381
+ last = state["messages"][-1]
382
+ assert isinstance(last, AIMessage)
383
+ call = last.tool_calls[0]
384
+ decision = request_tool_approval(str(call["id"]), call["name"], call["args"])
385
+ content = f"Booked {decision.args['city']}" if decision.approved else "Not booked"
386
+ return {"messages": [ToolMessage(content=content, tool_call_id=call["id"])]}
387
+
388
+
389
+ def route_to_tools_or_end(state: MessagesState) -> str:
390
+ return "tools" if getattr(state["messages"][-1], "tool_calls", None) else END
391
+
392
+
393
+ builder = StateGraph(MessagesState)
394
+ builder.add_node("model", call_model) # your chat-model node, e.g. ChatAnthropic + bind_tools
395
+ builder.add_node("tools", tools)
396
+ builder.add_edge(START, "model")
397
+ builder.add_conditional_edges("model", route_to_tools_or_end, ["tools", END])
398
+ builder.add_edge("tools", "model")
399
+
400
+
401
+ class ChatState(AgentState, rx.State):
402
+ agent: ClassVar = LangGraphAGUIAgent(builder.compile(checkpointer=InMemorySaver()))
403
+
404
+
405
+ def index() -> rx.Component:
406
+ return agent_chat(ChatState)
407
+ ```
408
+
409
+ - One agent instance serves every session; each run works on a fresh copy.
410
+ - Several interrupts at once are answered one at a time in the UI and resumed together,
411
+ each node receiving its own answer.
412
+ - Cancelling while paused resumes the next run with
413
+ `ApprovalDecision(approved=False, reason="cancelled")` for an approval, or the
414
+ `{"__agui_cancelled__": True}` value for a generic interrupt.
415
+ - `legacy_interrupts=True` supports integrations that only send the older `on_interrupt`
416
+ event.
417
+
418
+ ## Examples
419
+
420
+ | Example | What it is |
421
+ |---|---|
422
+ | [`examples/quickstart/`](examples/quickstart/) | The quickstart above: a chat with a PydanticAI agent in 23 lines |
423
+ | [`examples/pydantic_ai_dojo/`](examples/pydantic_ai_dojo/) | The five AG-UI Dojo demos with PydanticAI |
424
+ | [`examples/langgraph_dojo/`](examples/langgraph_dojo/) | The same five demos with LangGraph graphs |
425
+
426
+ The Dojo demos are agentic chat, backend tool rendering, human in the loop (approve, edit
427
+ or reject a tool call; answer a question), shared state and tool-based generative UI.
428
+ They use Claude by default; `AGENTKIT_MODEL=ollama:<model>` switches to a local Ollama
429
+ model, and `AGENTKIT_MODEL=test` to an offline model (see
430
+ [`examples/_shared`](examples/_shared/README.md)).
431
+
432
+ ## Development
433
+
434
+ The workflow is uv only (Python 3.11–3.13).
435
+
436
+ | Purpose | Command |
437
+ |---|---|
438
+ | Set up the environment | `uv sync --all-extras --all-packages` |
439
+ | Lint and format check | `uv run ruff check .` · `uv run ruff format --check .` |
440
+ | Type check (strict on `core` and `state`) | `uv run mypy` |
441
+ | Tests with coverage | `uv run pytest` |
442
+ | Coverage gate (≥ 85 % on `core` and `state`) | `uv run python scripts/check_gated_coverage.py` |
443
+ | Core isolation check | `uv run python scripts/assert_core_isolated.py` |
444
+ | Quickstart smoke (offline) | `uv run pytest tests/smoke` |
445
+ | Benchmarks (NFR-01, NFR-04) | `uv run pytest -m bench -q` |
446
+ | Docs site | `uv run mkdocs serve` · `uv run mkdocs build --strict` |
447
+
448
+ Releases are automatic: merging a new version into `main` publishes it to PyPI (see
449
+ [`CONTRIBUTING.md`](CONTRIBUTING.md#releases)).
450
+
451
+ Read [`CONTRIBUTING.md`](CONTRIBUTING.md) and [`specs/constitution.md`](specs/constitution.md)
452
+ before contributing. The rules: the specs come first, `core` and `state` are written
453
+ test-first, and changes use Conventional Commits.
454
+
455
+ ## License
456
+
457
+ MIT
@@ -0,0 +1,38 @@
1
+ reflex_agentkit/__init__.py,sha256=Ku0cuOQcdeZLQ3jWoyUS6QqRNDg0UuG_emwY_T5OKso,1798
2
+ reflex_agentkit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ reflex_agentkit/adapters/__init__.py,sha256=n9IlDT1P3hc2a9ijspsgJ77rFVdi332WXyhhUTaDkX0,61
4
+ reflex_agentkit/adapters/_require.py,sha256=U6IkO2Z30CUsDW98QKf_POAh9nP6TEldYBpiqodAM4M,915
5
+ reflex_agentkit/adapters/langgraph.py,sha256=YzltOq70njlmN9Nusa03_evtUDvk01C0qh7rPNNlaIc,11015
6
+ reflex_agentkit/adapters/pydantic_ai.py,sha256=NMWTK0p9RdgYvOUlKD3ICd_5Q-L5UzSRXJH204c41b8,3674
7
+ reflex_agentkit/components/__init__.py,sha256=2RGgiX-l_Qq2rvYX8BW1k65DkeEd3EjBRgjZtxBAq4s,758
8
+ reflex_agentkit/components/approval.py,sha256=0JUDnZxUdg2fLLpwZcMndWhwWapcJPdFfBFWcAlX_gc,4317
9
+ reflex_agentkit/components/chat.py,sha256=oHOEPhqj_KR3H0neGr9TTrotRY84wW9_uZmOuZj2lBw,1555
10
+ reflex_agentkit/components/composer.py,sha256=XUG1pKY1iIRFCx2lL2wu0wxWoBGBlFnYO2z1vxB_a20,1247
11
+ reflex_agentkit/components/messages.py,sha256=ym2NvSSaq5CfAl5-hyGxhUnxPwZRUAgg0qO19kcMzdo,3283
12
+ reflex_agentkit/components/shared_state.py,sha256=Lg_OTriMS5AAPwtUBuaXutPTysJK-JAjcA3QfSWWmiY,936
13
+ reflex_agentkit/components/status.py,sha256=hDm54yFfOWTqESFb4cCl0eZl4zED4ZUCa5Vfb_NURGo,1270
14
+ reflex_agentkit/components/tools.py,sha256=4eyvHnA85W4mVN1L5WLJ_jyn8X1k5favrO1RPf5TbSA,3022
15
+ reflex_agentkit/core/__init__.py,sha256=rUu0wnqkMbBeq8r-52EX6tUn8zNPjiJ13_0Umr39aVo,1311
16
+ reflex_agentkit/core/_log.py,sha256=EVHmNKUoX6P27edmTGwQ8zaeoDPxuPM1IbDkzk-L960,1623
17
+ reflex_agentkit/core/_patch.py,sha256=cqwcDCnDKjpX12_qaNKoyEpPiQJLZ-AZIl3sblpkJgc,1521
18
+ reflex_agentkit/core/commands.py,sha256=P2AQqR-qL5MfhTq0p3ZHHBDkONitdHdHcRWnYAuRqfQ,22753
19
+ reflex_agentkit/core/conversation.py,sha256=2m5OZPQe5yX8EKoO9VvAWqT34frWxGtacrqcxbOaQqk,3458
20
+ reflex_agentkit/core/errors.py,sha256=DPEkpDW8SyDdBkcNQtKYsT6QHRy8riBICFs9YBiJJz8,2470
21
+ reflex_agentkit/core/models.py,sha256=NpqlpT3NcgJW0R39s3WWDtVtAZCOjFqwtrzw5F4cJPo,7486
22
+ reflex_agentkit/core/reducer.py,sha256=gVWZt9UhcXqjbbdPn4atAIEj7z5fH9Ao0nWM_mtkZZA,42291
23
+ reflex_agentkit/core/run_input.py,sha256=IZJw6gwEyDNo-bbXgIFN_Pj2Z2usoOPaRN2QypckmqE,4292
24
+ reflex_agentkit/core/tools.py,sha256=1xbbI7sAO1Xnplm2Dkh8NvqKj6D5yphNAEMfSyJhu1I,7527
25
+ reflex_agentkit/core/clients/__init__.py,sha256=Bj_l9IvQg4vqq6QU0-kVw5ueODWOydghf1iiFu3N480,453
26
+ reflex_agentkit/core/clients/base.py,sha256=AHrnuDQiFw9PgKMeKq3X9TcGlc0ge5OyL6UyhYGzOck,2627
27
+ reflex_agentkit/core/clients/http.py,sha256=ORp9CrPlzc9wgt09KVK2W1eIpeLTCnNVu_lVqt1cM5c,5650
28
+ reflex_agentkit/core/clients/in_process.py,sha256=tH_wX3LuaLNz-l5udON0_hEn0ByOFxpb_iLOvwWQ7xs,2648
29
+ reflex_agentkit/core/clients/sse.py,sha256=I5BGu7ujYFsBcSazt9-upvqqixLDVINk5vpBAS7It9I,2871
30
+ reflex_agentkit/state/__init__.py,sha256=XcTzdMxvPZommiOw34wbERSGLv8USIYvVO-SP6lnupk,130
31
+ reflex_agentkit/state/_driver.py,sha256=jAOc28KLU_hz_8vqCpbvmqNt7uK-v7A4PVkiZdvb5oU,22915
32
+ reflex_agentkit/state/_projection.py,sha256=ogbJXRJ8xogCIP7OOFXND-i44EWHlmOFE_YI-iVrx9I,4164
33
+ reflex_agentkit/state/_registry.py,sha256=pdrIoDv_CgV6Su3nWS4Pzq1qh9s-ClCRFqD49GKz7B0,1622
34
+ reflex_agentkit/state/agent_state.py,sha256=acpO64_vtOX4a7CQeOaCwDsmlsRAZ7LovwK5H2rRgKg,20859
35
+ reflex_agentkit-0.1.0.dist-info/METADATA,sha256=4YxjT9LTHgF9gLlwo-kwwci4UIcoGiHJTHRojZHGj_Q,19819
36
+ reflex_agentkit-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
37
+ reflex_agentkit-0.1.0.dist-info/licenses/LICENSE,sha256=ikDZVzB6LJXytfIXkllDZj9ningVU9BduOAcL8jLKYI,1071
38
+ reflex_agentkit-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ernesto Crespo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.