Skip to content

Commit a75fd36

Browse files
committed
docs: add context routing documentation
README feature bullet, fixtures page match-field table row and JSON example, multi-turn chooser table row and gotcha, record-replay context-aware recording section, and landing page highlight card.
1 parent 3df00b8 commit a75fd36

5 files changed

Lines changed: 120 additions & 10 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ Run them all on one port with `npx @copilotkit/aimock --config aimock.json`, or
5656
- **[MCP](https://aimock.copilotkit.dev/mcp-mock) / [A2A](https://aimock.copilotkit.dev/a2a-mock) / [AG-UI](https://aimock.copilotkit.dev/agui-mock) / [Vector](https://aimock.copilotkit.dev/vector-mock)** — Mock every protocol your AI agents use
5757
- **[Chaos Testing](https://aimock.copilotkit.dev/chaos-testing)** — 500 errors, malformed JSON, mid-stream disconnects at any probability
5858
- **Per-Request Strict Mode**`X-AIMock-Strict` header overrides the server-level `--strict` flag per request (`true`/`1` = strict, `false`/`0` = lenient)
59+
- **Context-Based Fixture Routing**`X-AIMock-Context` header scopes fixtures per integration; fixtures with `match.context` only match requests carrying that context, fixtures without it remain shared
5960
- **[Drift Detection](https://aimock.copilotkit.dev/drift-detection)** — Daily CI validation against real APIs
6061
- **[Streaming Physics](https://aimock.copilotkit.dev/streaming-physics)** — Configurable `ttft`, `tps`, and `jitter`
6162
- **[WebSocket APIs](https://aimock.copilotkit.dev/websocket)** — OpenAI Realtime (GA protocol with models: gpt-realtime, gpt-realtime-2, gpt-realtime-1.5, gpt-realtime-mini; transcription/translation via gpt-4o-transcribe, gpt-4o-mini-transcribe, whisper-1; image input; commentary phase), Responses WS, Gemini Live

docs/docs/index.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,14 @@ <h2>What's New</h2>
290290
<p class="highlight-card-desc">One-line CI setup for mock-backed test suites</p>
291291
<a href="/github-action" class="highlight-card-cta">Setup <span>&rarr;</span></a>
292292
</div>
293+
294+
<div class="highlight-card">
295+
<span class="highlight-card-title">Context Routing</span>
296+
<p class="highlight-card-desc">
297+
Scope fixtures per integration with <code>X-AIMock-Context</code> header
298+
</p>
299+
<a href="/fixtures#context" class="highlight-card-cta">Docs <span>&rarr;</span></a>
300+
</div>
293301
</div>
294302

295303
<!-- ─── The Suite (compact table) ───────────────────────────── -->

docs/fixtures/index.html

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,16 @@ <h2>Match Fields</h2>
160160
their own fixture APIs rather than via this field
161161
</td>
162162
</tr>
163+
<tr>
164+
<td>context</td>
165+
<td>string</td>
166+
<td>
167+
Restrict to a named context via <code>X-AIMock-Context</code> header. Fixtures with
168+
<code>context</code> only match requests carrying that exact value; fixtures without
169+
<code>context</code> match any request. Same opt-in semantics as
170+
<code>endpoint</code>
171+
</td>
172+
</tr>
163173
<tr>
164174
<td>predicate</td>
165175
<td>function</td>
@@ -242,14 +252,15 @@ <h3>5. Validation warnings surface shadowing at load time</h3>
242252
<code>duplicate userMessage 'hello' — shadows fixture 0</code>, where
243253
<code>'hello'</code> is the duplicated message and <code>0</code> is the zero-based
244254
index of the earlier fixture being shadowed. This is advisory, not a hard error: the
245-
check now factors in <code>turnIndex</code>, <code>hasToolResult</code>, and
246-
<code>sequenceIndex</code> when deciding whether two fixtures truly collide, but it does
247-
<em>not</em> consider <code>toolCallId</code>, <code>model</code>, or
248-
<code>predicate</code>, so the warning may still fire when those discriminators are
249-
present. Treat it as advisory: if a runtime differentiator is in place, the fixtures
250-
won't actually shadow each other at match time. Only fixtures with no differentiator at
251-
all will truly shadow on match &mdash; that's the case where the second is never reached
252-
because the first wins. Safe to ignore in the former case; investigate in the latter.
255+
check now factors in <code>turnIndex</code>, <code>hasToolResult</code>,
256+
<code>context</code>, and <code>sequenceIndex</code> when deciding whether two fixtures
257+
truly collide, but it does <em>not</em> consider <code>toolCallId</code>,
258+
<code>model</code>, or <code>predicate</code>, so the warning may still fire when those
259+
discriminators are present. Treat it as advisory: if a runtime differentiator is in
260+
place, the fixtures won't actually shadow each other at match time. Only fixtures with
261+
no differentiator at all will truly shadow on match &mdash; that's the case where the
262+
second is never reached because the first wins. Safe to ignore in the former case;
263+
investigate in the latter.
253264
</li>
254265
<li>
255266
<strong>Catch-all not last</strong> &mdash; a fixture with an empty <code>match</code>
@@ -511,6 +522,29 @@ <h3>From a directory</h3>
511522
</p>
512523
</div>
513524

525+
<h3 id="context">Context-scoped fixtures</h3>
526+
<div class="code-block">
527+
<div class="code-block-header">
528+
fixtures/context-example.json <span class="lang-tag">json</span>
529+
</div>
530+
<pre><code>{
531+
<span class="key">"fixtures"</span>: [
532+
{
533+
<span class="key">"match"</span>: { <span class="key">"userMessage"</span>: <span class="str">"hello"</span>, <span class="key">"context"</span>: <span class="str">"langgraph-python"</span> },
534+
<span class="key">"response"</span>: { <span class="key">"content"</span>: <span class="str">"Hi from LangGraph!"</span> }
535+
},
536+
{
537+
<span class="key">"match"</span>: { <span class="key">"userMessage"</span>: <span class="str">"hello"</span> },
538+
<span class="key">"response"</span>: { <span class="key">"content"</span>: <span class="str">"Hi from the shared fallback!"</span> }
539+
}
540+
]
541+
}</code></pre>
542+
</div>
543+
<p>
544+
Requests with <code>X-AIMock-Context: langgraph-python</code> match the first fixture; all
545+
other requests fall through to the shared fixture.
546+
</p>
547+
514548
<h3>Programmatically</h3>
515549
<div class="code-block">
516550
<div class="code-block-header">programmatic.ts <span class="lang-tag">ts</span></div>

docs/multi-turn/index.html

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,10 @@ <h3>Turn 2 &mdash; client runs the tool, sends the result</h3>
291291
</p>
292292
</div>
293293

294-
<h2>Choosing between sequenceIndex, toolCallId, turnIndex, hasToolResult, and predicate</h2>
294+
<h2>
295+
Choosing between sequenceIndex, toolCallId, turnIndex, hasToolResult, context, and
296+
predicate
297+
</h2>
295298
<p>Five mechanisms handle different shapes of &ldquo;the same prompt twice&rdquo;:</p>
296299

297300
<table class="endpoint-table">
@@ -341,6 +344,15 @@ <h2>Choosing between sequenceIndex, toolCallId, turnIndex, hasToolResult, and pr
341344
pinning a specific <code>tool_call_id</code>.
342345
</td>
343346
</tr>
347+
<tr>
348+
<td>Same user prompt, different response per integration or caller identity</td>
349+
<td><code>context</code></td>
350+
<td>
351+
Exact match on the <code>X-AIMock-Context</code> header. Fixtures with
352+
<code>context</code> only match requests carrying that value; fixtures without it
353+
remain shared. Stateless.
354+
</td>
355+
</tr>
344356
<tr>
345357
<td>
346358
Arbitrary inspection &mdash; message count, specific content at any position, custom
@@ -442,6 +454,15 @@ <h2 id="gotchas">Gotchas</h2>
442454
instance. See <a href="/sequential-responses">Sequential Responses</a> for when
443455
<code>sequenceIndex</code> is the right tool.
444456
</li>
457+
<li>
458+
<strong><code>context</code> is an additional discriminator, not a replacement.</strong>
459+
<code>context</code> scopes fixtures by integration identity (<code
460+
>X-AIMock-Context</code
461+
>
462+
header). It combines with all other match fields via AND. Two fixtures with the same
463+
<code>userMessage</code> but different <code>context</code>
464+
values are not duplicates &mdash; the validator accounts for this.
465+
</li>
445466
</ul>
446467
</main>
447468
<aside class="page-toc" id="page-toc"></aside>

docs/record-replay/index.html

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,49 @@ <h2 id="model-aware-recording">Model-Aware Recording</h2>
556556
});</code></pre>
557557
</div>
558558

559+
<h2 id="context-aware-recording">Context-Aware Recording</h2>
560+
<p>
561+
When a request carries an <code>X-AIMock-Context</code> header, the recorder automatically
562+
captures the context value in <code>match.context</code>. On replay, fixtures with
563+
<code>context</code> only match requests carrying that exact header value &mdash; fixtures
564+
without <code>context</code> remain shared across all callers.
565+
</p>
566+
567+
<h3>Directory routing</h3>
568+
<p>
569+
Without snapshot-style recording (<code>X-Test-Id</code>), recorded fixtures for a given
570+
context are written to a <code>&lt;fixturePath&gt;/&lt;context&gt;/</code> subdirectory:
571+
</p>
572+
573+
<div class="code-block">
574+
<div class="code-block-header">
575+
Context directory layout <span class="lang-tag">text</span>
576+
</div>
577+
<pre><code>fixtures/recorded/
578+
openai-2026-05-18T10-30-00-000Z-a1b2c3d4.json # no context (shared)
579+
langgraph-python/
580+
openai-2026-05-18T10-30-01-000Z-e5f6a7b8.json # context = langgraph-python
581+
crewai/
582+
openai-2026-05-18T10-30-02-000Z-c9d0e1f2.json # context = crewai</code></pre>
583+
</div>
584+
585+
<p>
586+
When <code>X-Test-Id</code> is also present, snapshot-style paths take precedence and the
587+
context is captured only in <code>match.context</code> within the fixture file, not in the
588+
directory structure.
589+
</p>
590+
591+
<h3>Sending <code>X-AIMock-Context</code></h3>
592+
<div class="code-block">
593+
<div class="code-block-header">Header example <span class="lang-tag">ts</span></div>
594+
<pre><code><span class="cm">// Set as a default header on your LLM client</span>
595+
<span class="kw">const</span> <span class="op">client</span> = <span class="kw">new</span> <span class="fn">OpenAI</span>({
596+
<span class="prop">baseURL</span>: <span class="str">"http://localhost:4010/v1"</span>,
597+
<span class="prop">apiKey</span>: <span class="str">"mock"</span>,
598+
<span class="prop">defaultHeaders</span>: { <span class="str">"X-AIMock-Context"</span>: <span class="str">"langgraph-python"</span> },
599+
});</code></pre>
600+
</div>
601+
559602
<h2 id="upstream-timeouts">Upstream Timeouts</h2>
560603

561604
<p>
@@ -858,7 +901,10 @@ <h2 id="recording-multi-turn-conversations">Recording Multi-Turn Conversations</
858901
}
859902
<span class="cm">// Chat/multimedia — key on the LAST user message only</span>
860903
<span class="kw">const</span> lastUser = <span class="fn">getLastMessageByRole</span>(request.messages, <span class="str">"user"</span>);
861-
<span class="kw">return</span> { <span class="prop">userMessage</span>: <span class="fn">getTextContent</span>(lastUser.content) };
904+
<span class="kw">const</span> match = { <span class="prop">userMessage</span>: <span class="fn">getTextContent</span>(lastUser.content) };
905+
<span class="cm">// Capture context from X-AIMock-Context header if present</span>
906+
<span class="kw">if</span> (request._context) match.<span class="prop">context</span> = request._context;
907+
<span class="kw">return</span> match;
862908
}</code></pre>
863909
</div>
864910

0 commit comments

Comments
 (0)