Skip to content

Commit 75d0dfa

Browse files
iskhakovtclaude
andcommitted
docs: add requestTransform section to record-replay docs
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1 parent 958add3 commit 75d0dfa

File tree

1 file changed

+61
-0
lines changed

1 file changed

+61
-0
lines changed

docs/record-replay.html

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,67 @@ <h2>Strict Mode</h2>
263263
unexpected API calls.
264264
</p>
265265

266+
<h2>Request Transform</h2>
267+
<p>
268+
When upstream services inject dynamic data into prompts — timestamps, UUIDs, session IDs,
269+
or per-request metadata — the same logical request produces different raw text every time.
270+
Recorded fixtures won't replay because the text never matches exactly.
271+
<code>requestTransform</code> normalizes requests <em>before</em> fixture matching and
272+
recording, stripping the volatile parts so that logically identical requests always hit
273+
the same fixture.
274+
</p>
275+
<p>
276+
<strong>Matching behavior change:</strong> when a <code>requestTransform</code> is set,
277+
string comparisons for <code>userMessage</code> and <code>inputText</code> switch from
278+
substring (<code>includes()</code>) to exact equality (<code>===</code>). This ensures
279+
deterministic replay of recorded fixtures — no accidental partial matches against
280+
normalized text. RegExp and predicate matching are unaffected; predicates always receive
281+
the original (untransformed) request.
282+
</p>
283+
284+
<div class="code-block">
285+
<div class="code-block-header">
286+
Stripping dynamic fields <span class="lang-tag">ts</span>
287+
</div>
288+
<pre><code><span class="kw">import</span> { <span class="op">LLMock</span> } <span class="kw">from</span> <span class="str">"@copilotkit/llmock"</span>;
289+
<span class="kw">import</span> <span class="kw">type</span> { <span class="op">ChatCompletionRequest</span> } <span class="kw">from</span> <span class="str">"@copilotkit/llmock"</span>;
290+
291+
<span class="cmt">// Strip timestamps and request IDs injected by the orchestrator</span>
292+
<span class="kw">function</span> <span class="fn">normalize</span>(<span class="op">req</span>: <span class="op">ChatCompletionRequest</span>): <span class="op">ChatCompletionRequest</span> {
293+
<span class="kw">return</span> {
294+
...<span class="op">req</span>,
295+
<span class="prop">messages</span>: <span class="op">req</span>.<span class="prop">messages</span>.<span class="fn">map</span>((<span class="op">m</span>) =&gt; {
296+
<span class="kw">if</span> (<span class="op">m</span>.<span class="prop">role</span> !== <span class="str">"system"</span>) <span class="kw">return</span> <span class="op">m</span>;
297+
<span class="kw">if</span> (<span class="kw">typeof</span> <span class="op">m</span>.<span class="prop">content</span> !== <span class="str">"string"</span>) <span class="kw">return</span> <span class="op">m</span>;
298+
<span class="kw">return</span> {
299+
...<span class="op">m</span>,
300+
<span class="prop">content</span>: <span class="op">m</span>.<span class="prop">content</span>
301+
.<span class="fn">replace</span>(<span class="str">/Current time: .*/g</span>, <span class="str">""</span>)
302+
.<span class="fn">replace</span>(<span class="str">/Session: [a-f0-9-]{36}/g</span>, <span class="str">""</span>)
303+
.<span class="fn">trim</span>(),
304+
};
305+
}),
306+
};
307+
}
308+
309+
<span class="kw">const</span> <span class="op">mock</span> = <span class="kw">new</span> <span class="fn">LLMock</span>({
310+
<span class="prop">requestTransform</span>: <span class="op">normalize</span>,
311+
});
312+
<span class="kw">await</span> <span class="op">mock</span>.<span class="fn">start</span>();
313+
314+
<span class="op">mock</span>.<span class="fn">enableRecording</span>({
315+
<span class="prop">providers</span>: { <span class="prop">openai</span>: <span class="str">"https://api.openai.com"</span> },
316+
<span class="prop">fixturePath</span>: <span class="str">"./fixtures/recorded"</span>,
317+
});</code></pre>
318+
</div>
319+
320+
<p>
321+
The transform is applied in two places: during fixture <strong>matching</strong> (so
322+
replayed requests find the right fixture) and during <strong>recording</strong> (so the
323+
saved fixture's match key is already normalized). This means a fixture recorded through a
324+
transform will replay correctly on the next run without any manual editing.
325+
</p>
326+
266327
<h2>Fixture Auto-Generation</h2>
267328
<p>Recorded fixtures are saved to disk with timestamped filenames:</p>
268329

0 commit comments

Comments
 (0)