Skip to content

Commit 819ea26

Browse files
authored
Add AGUIMock — AG-UI protocol mock (#100)
## Summary - New `AGUIMock` class implementing the AG-UI (Agent-to-UI) protocol — the SSE event streaming protocol CopilotKit uses between agents and the frontend - All 33 AG-UI event types supported (lifecycle, text, tool calls, state, activity, reasoning, special, deprecated) - 11 event builder functions for common patterns (text response, tool call, state update, reasoning, activity, etc.) - Fluent registration API: `onMessage()`, `onRun()`, `onToolCall()`, `onStateKey()`, `onReasoning()`, `onPredicate()` - Record & replay with tee streaming — proxy to real AG-UI agents, record event streams as fixtures, replay on subsequent requests - Proxy-only mode (`--agui-proxy-only`) — forward without saving or caching - Standalone and mountable modes (mount at `/agui` on LLMock) - Config file, suite, and CLI support (`--agui-record`, `--agui-upstream`, `--agui-proxy-only`) ## Use case Frontend developers can test CopilotKit UI components without a running agent backend. Point the frontend at aimock's `/agui` endpoint and get deterministic AG-UI event streams from fixtures. For demos, use proxy-only mode with canned fixtures for repeatable scenarios while proxying unknown traffic to the real agent. ```ts const agui = new AGUIMock(); agui.onMessage("hello", "Hi! How can I help?"); agui.onToolCall(/search/, "web_search", '{"q":"test"}', { result: "[]" }); agui.enableRecording({ upstream: "http://localhost:8000/agent", proxyOnly: true }); const llm = new LLMock(); llm.mount("/agui", agui); await llm.start(); ``` ## New files - `src/agui-types.ts` — 33 event type definitions, fixture/match types - `src/agui-handler.ts` — matching, 11 builders, SSE writer - `src/agui-mock.ts` — AGUIMock class (Mountable) - `src/agui-recorder.ts` — record/replay with tee streaming, proxy-only mode - `src/agui-stub.ts` — barrel exports - `src/__tests__/agui-mock.test.ts` — 32 tests ## Modified files - `src/config-loader.ts` — AGUIConfig support - `src/suite.ts` — agui option in MockSuite - `src/index.ts` — AG-UI exports - `src/cli.ts` — AG-UI recording flags ## Test plan - [x] 32 new tests (core 14, builders 5, edge cases 7, record/replay 6) - [x] All 2246 tests pass - [x] Build passes - [x] 3-round CR loop converged at 0 findings 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents 486ccd9 + 3f36d90 commit 819ea26

File tree

58 files changed

+3352
-322
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+3352
-322
lines changed

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"source": {
1010
"source": "npm",
1111
"package": "@copilotkit/aimock",
12-
"version": "^1.10.0"
12+
"version": "^1.11.0"
1313
},
1414
"description": "Fixture authoring skill for @copilotkit/aimock — match fields, response types, embeddings, structured output, sequential responses, streaming physics, agent loop patterns, gotchas, and debugging"
1515
}

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "llmock",
3-
"version": "1.10.0",
3+
"version": "1.11.0",
44
"description": "Fixture authoring guidance for @copilotkit/aimock",
55
"author": {
66
"name": "CopilotKit"

.github/workflows/publish-docker.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ on:
77
pull_request:
88
branches:
99
- main
10-
workflow_dispatch:
1110

1211
env:
1312
REGISTRY: ghcr.io

.github/workflows/test-drift.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,32 @@ name: Drift Tests
22
on:
33
schedule:
44
- cron: "0 6 * * *" # Daily 6am UTC
5+
pull_request:
6+
paths:
7+
- "src/agui-types.ts"
8+
- "src/__tests__/drift/agui-schema.drift.ts"
59
workflow_dispatch: # Manual trigger
610
jobs:
11+
agui-schema-drift:
12+
runs-on: ubuntu-latest
13+
timeout-minutes: 5
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: pnpm/action-setup@v4
17+
- uses: actions/setup-node@v4
18+
with:
19+
node-version: 22
20+
cache: pnpm
21+
- run: pnpm install --frozen-lockfile
22+
23+
- name: Clone ag-ui repo
24+
run: git clone --depth 1 https://github.com/ag-ui-protocol/ag-ui.git ../ag-ui
25+
26+
- name: Run AG-UI schema drift test
27+
run: npx vitest run src/__tests__/drift/agui-schema.drift.ts --config vitest.config.drift.ts
28+
729
drift:
30+
if: github.event_name != 'pull_request'
831
runs-on: ubuntu-latest
932
timeout-minutes: 15
1033
steps:

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# @copilotkit/aimock
22

3+
## 1.11.0
4+
5+
### Minor Changes
6+
7+
- Add `AGUIMock` — mock the AG-UI (Agent-to-UI) protocol for CopilotKit frontend testing. All 33 event types, 11 convenience builders, fluent registration API, SSE streaming with disconnect handling (#100)
8+
- Add AG-UI record & replay with tee streaming — proxy to real AG-UI agents, record event streams as fixtures, replay on subsequent requests. Includes `--proxy-only` mode for demos (#100)
9+
- Add AG-UI schema drift detection — compares aimock event types against canonical `@ag-ui/core` Zod schemas to catch protocol changes (#100)
10+
- Add `--agui-record`, `--agui-upstream`, `--agui-proxy-only` CLI flags (#100)
11+
- Remove section bar from docs pages (cleanup)
12+
313
## 1.10.0
414

515
### Minor Changes

charts/aimock/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ name: aimock
33
description: Mock infrastructure for AI application testing (OpenAI, Anthropic, Gemini, MCP, A2A, vector)
44
type: application
55
version: 0.1.0
6-
appVersion: "1.10.0"
6+
appVersion: "1.11.0"

docs/a2a-mock/index.html

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@
4343
</div>
4444
</nav>
4545

46-
<div id="section-bar"></div>
47-
4846
<div class="docs-layout">
4947
<aside class="sidebar" id="sidebar"></aside>
5048

docs/agui-mock/index.html

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>AG-UI Mock — aimock</title>
7+
<link rel="icon" type="image/svg+xml" href="../favicon.svg" />
8+
<link rel="preconnect" href="https://fonts.googleapis.com" />
9+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10+
<link
11+
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&family=Instrument+Sans:wght@400;500;600;700&display=swap"
12+
rel="stylesheet"
13+
/>
14+
<link rel="stylesheet" href="../style.css" />
15+
</head>
16+
<body>
17+
<nav class="top-nav">
18+
<div class="nav-inner">
19+
<div style="display: flex; align-items: center; gap: 1rem">
20+
<button
21+
class="sidebar-toggle"
22+
onclick="document.querySelector('.sidebar').classList.toggle('open')"
23+
aria-label="Toggle sidebar"
24+
>
25+
&#9776;
26+
</button>
27+
<a href="/" class="nav-brand"> <span class="prompt">$</span> aimock </a>
28+
</div>
29+
<ul class="nav-links">
30+
<li><a href="/">Home</a></li>
31+
<li><a href="/docs" style="color: var(--accent)">Docs</a></li>
32+
<li>
33+
<a href="https://github.com/CopilotKit/aimock" class="gh-link" target="_blank"
34+
><svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
35+
<path
36+
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"
37+
/>
38+
</svg>
39+
GitHub</a
40+
>
41+
</li>
42+
</ul>
43+
</div>
44+
</nav>
45+
46+
<div class="docs-layout">
47+
<aside class="sidebar" id="sidebar"></aside>
48+
49+
<main class="docs-content">
50+
<h1>AGUIMock</h1>
51+
<p class="lead">
52+
Mock the AG-UI (Agent-to-UI) protocol for CopilotKit frontend testing. Point your frontend
53+
at aimock instead of a real agent backend and get deterministic SSE event streams from
54+
fixtures.
55+
</p>
56+
57+
<h2>Quick Start</h2>
58+
<div class="code-block">
59+
<div class="code-block-header">
60+
Standalone mode <span class="lang-tag">typescript</span>
61+
</div>
62+
<pre><code><span class="kw">import</span> { AGUIMock } <span class="kw">from</span> <span class="str">"@copilotkit/aimock"</span>;
63+
64+
<span class="kw">const</span> agui = <span class="kw">new</span> AGUIMock();
65+
agui.<span class="fn">onMessage</span>(<span class="str">"hello"</span>, <span class="str">"Hi! How can I help?"</span>);
66+
agui.<span class="fn">onToolCall</span>(<span class="op">/search/</span>, <span class="str">"web_search"</span>, <span class="str">'{"q":"test"}'</span>, { <span class="prop">result</span>: <span class="str">"[]"</span> });
67+
68+
<span class="kw">const</span> url = <span class="kw">await</span> agui.<span class="fn">start</span>();
69+
<span class="cm">// POST to url with RunAgentInput body</span></code></pre>
70+
</div>
71+
72+
<h2>How It Works</h2>
73+
<ol>
74+
<li>Client sends POST with <code>RunAgentInput</code> JSON body</li>
75+
<li>AGUIMock matches the request against registered fixtures</li>
76+
<li>On match: streams back AG-UI events as SSE</li>
77+
<li>On miss with recording enabled: proxies to upstream, records events</li>
78+
<li>On miss without recording: returns 404</li>
79+
</ol>
80+
81+
<h2>Registration API</h2>
82+
<p>Fluent methods for registering fixture responses:</p>
83+
<table class="endpoint-table">
84+
<thead>
85+
<tr>
86+
<th>Method</th>
87+
<th>Match on</th>
88+
<th>Response</th>
89+
</tr>
90+
</thead>
91+
<tbody>
92+
<tr>
93+
<td><code>onMessage(pattern, text)</code></td>
94+
<td>Last user message</td>
95+
<td>Text response events</td>
96+
</tr>
97+
<tr>
98+
<td><code>onRun(pattern, events)</code></td>
99+
<td>Last user message</td>
100+
<td>Raw event sequence</td>
101+
</tr>
102+
<tr>
103+
<td><code>onToolCall(pattern, name, args, opts?)</code></td>
104+
<td>Last user message</td>
105+
<td>Tool call events</td>
106+
</tr>
107+
<tr>
108+
<td><code>onStateKey(key, snapshot)</code></td>
109+
<td>State key presence</td>
110+
<td>State snapshot</td>
111+
</tr>
112+
<tr>
113+
<td><code>onReasoning(pattern, text)</code></td>
114+
<td>Last user message</td>
115+
<td>Reasoning events</td>
116+
</tr>
117+
<tr>
118+
<td><code>onPredicate(fn, events)</code></td>
119+
<td>Custom function</td>
120+
<td>Raw event sequence</td>
121+
</tr>
122+
</tbody>
123+
</table>
124+
125+
<h2>Event Types</h2>
126+
<p>AG-UI protocol event categories:</p>
127+
<table class="endpoint-table">
128+
<thead>
129+
<tr>
130+
<th>Category</th>
131+
<th>Events</th>
132+
<th>Description</th>
133+
</tr>
134+
</thead>
135+
<tbody>
136+
<tr>
137+
<td>Lifecycle</td>
138+
<td>
139+
<code>RUN_STARTED</code>, <code>RUN_FINISHED</code>, <code>RUN_ERROR</code>,
140+
<code>STEP_STARTED</code>, <code>STEP_FINISHED</code>
141+
</td>
142+
<td>Run management</td>
143+
</tr>
144+
<tr>
145+
<td>Text</td>
146+
<td>
147+
<code>TEXT_MESSAGE_START</code>, <code>TEXT_MESSAGE_CONTENT</code>,
148+
<code>TEXT_MESSAGE_END</code>, <code>TEXT_MESSAGE_CHUNK</code>
149+
</td>
150+
<td>Streaming text</td>
151+
</tr>
152+
<tr>
153+
<td>Tool Calls</td>
154+
<td>
155+
<code>TOOL_CALL_START</code>, <code>TOOL_CALL_ARGS</code>,
156+
<code>TOOL_CALL_END</code>, <code>TOOL_CALL_RESULT</code>
157+
</td>
158+
<td>Tool execution</td>
159+
</tr>
160+
<tr>
161+
<td>State</td>
162+
<td>
163+
<code>STATE_SNAPSHOT</code>, <code>STATE_DELTA</code>,
164+
<code>MESSAGES_SNAPSHOT</code>
165+
</td>
166+
<td>Frontend state sync</td>
167+
</tr>
168+
<tr>
169+
<td>Activity</td>
170+
<td><code>ACTIVITY_SNAPSHOT</code>, <code>ACTIVITY_DELTA</code></td>
171+
<td>Progress indicators</td>
172+
</tr>
173+
<tr>
174+
<td>Reasoning</td>
175+
<td><code>REASONING_START</code>, ..., <code>REASONING_END</code></td>
176+
<td>Chain of thought</td>
177+
</tr>
178+
<tr>
179+
<td>Special</td>
180+
<td><code>RAW</code>, <code>CUSTOM</code></td>
181+
<td>Extensibility</td>
182+
</tr>
183+
</tbody>
184+
</table>
185+
186+
<h2>Event Builders</h2>
187+
<p>Convenience functions for constructing event sequences:</p>
188+
<table class="endpoint-table">
189+
<thead>
190+
<tr>
191+
<th>Builder</th>
192+
<th>Returns</th>
193+
</tr>
194+
</thead>
195+
<tbody>
196+
<tr>
197+
<td><code>buildTextResponse(text)</code></td>
198+
<td>Full text response with lifecycle</td>
199+
</tr>
200+
<tr>
201+
<td><code>buildToolCallResponse(name, args)</code></td>
202+
<td>Tool call with optional result</td>
203+
</tr>
204+
<tr>
205+
<td><code>buildStateUpdate(snapshot)</code></td>
206+
<td>State snapshot</td>
207+
</tr>
208+
<tr>
209+
<td><code>buildStateDelta(patches)</code></td>
210+
<td>JSON Patch incremental update</td>
211+
</tr>
212+
<tr>
213+
<td><code>buildErrorResponse(message)</code></td>
214+
<td>Error termination</td>
215+
</tr>
216+
<tr>
217+
<td><code>buildCompositeResponse(outputs[])</code></td>
218+
<td>Multiple builders merged</td>
219+
</tr>
220+
</tbody>
221+
</table>
222+
223+
<h2>Record &amp; Replay</h2>
224+
<p>
225+
Record mode proxies unmatched requests to an upstream agent, saves the event stream as a
226+
fixture, and replays it on subsequent matches. Proxy-only mode forwards every time without
227+
saving, ideal for demos mixing canned and live scenarios.
228+
</p>
229+
<div class="code-block">
230+
<div class="code-block-header">
231+
Recording setup <span class="lang-tag">typescript</span>
232+
</div>
233+
<pre><code><span class="kw">const</span> agui = <span class="kw">new</span> AGUIMock();
234+
agui.<span class="fn">onMessage</span>(<span class="str">"hello"</span>, <span class="str">"Hi!"</span>); <span class="cm">// known scenario</span>
235+
agui.<span class="fn">enableRecording</span>({
236+
upstream: <span class="str">"http://localhost:8000/agent"</span>,
237+
proxyOnly: <span class="kw">true</span>, <span class="cm">// false to save fixtures</span>
238+
});</code></pre>
239+
</div>
240+
241+
<h2>CLI Usage</h2>
242+
<div class="code-block">
243+
<div class="code-block-header">CLI flags <span class="lang-tag">shell</span></div>
244+
<pre><code>npx aimock --fixtures ./fixtures \
245+
--agui-record \
246+
--agui-upstream http://localhost:8000/agent</code></pre>
247+
</div>
248+
<p>
249+
Flags: <code>--agui-record</code>, <code>--agui-upstream</code>,
250+
<code>--agui-proxy-only</code>
251+
</p>
252+
253+
<h2>JSON Config</h2>
254+
<div class="code-block">
255+
<div class="code-block-header">aimock.json <span class="lang-tag">json</span></div>
256+
<pre><code>{
257+
<span class="prop">"agui"</span>: {
258+
<span class="prop">"path"</span>: <span class="str">"/agui"</span>,
259+
<span class="prop">"fixtures"</span>: [
260+
{ <span class="prop">"match"</span>: { <span class="prop">"message"</span>: <span class="str">"hello"</span> }, <span class="prop">"text"</span>: <span class="str">"Hi!"</span> }
261+
]
262+
}
263+
}</code></pre>
264+
</div>
265+
266+
<h2>Mounting</h2>
267+
<p>
268+
AGUIMock implements <code>Mountable</code> and can be mounted at any path on an LLMock
269+
server via <code>llm.mount("/agui", agui)</code>. See
270+
<a href="/mount">Mount &amp; Composition</a> for details.
271+
</p>
272+
</main>
273+
<aside class="page-toc" id="page-toc"></aside>
274+
</div>
275+
<footer class="docs-footer">
276+
<div class="footer-inner">
277+
<div class="footer-left"><span>$</span> aimock &middot; MIT License</div>
278+
<ul class="footer-links">
279+
<li><a href="https://github.com/CopilotKit/aimock" target="_blank">GitHub</a></li>
280+
<li>
281+
<a href="https://www.npmjs.com/package/@copilotkit/aimock" target="_blank">npm</a>
282+
</li>
283+
</ul>
284+
</div>
285+
</footer>
286+
<script src="../sidebar.js"></script>
287+
<script src="../cli-tabs.js"></script>
288+
</body>
289+
</html>

docs/aimock-cli/index.html

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@
4343
</div>
4444
</nav>
4545

46-
<div id="section-bar"></div>
47-
4846
<div class="docs-layout">
4947
<aside class="sidebar" id="sidebar"></aside>
5048

docs/aws-bedrock/index.html

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@
4343
</div>
4444
</nav>
4545

46-
<div id="section-bar"></div>
47-
4846
<div class="docs-layout">
4947
<aside class="sidebar" id="sidebar"></aside>
5048

0 commit comments

Comments
 (0)