Skip to content

Commit b27cf01

Browse files
committed
docs: add fal.ai documentation page
1 parent 9bbaa09 commit b27cf01

2 files changed

Lines changed: 257 additions & 0 deletions

File tree

docs/fal-ai/index.html

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
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>fal.ai — 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+
<script src="/pixels.js" defer></script>
16+
</head>
17+
<body>
18+
<nav class="top-nav">
19+
<div class="nav-inner">
20+
<div style="display: flex; align-items: center; gap: 1rem">
21+
<button
22+
class="sidebar-toggle"
23+
onclick="document.querySelector('.sidebar').classList.toggle('open')"
24+
aria-label="Toggle sidebar"
25+
>
26+
&#9776;
27+
</button>
28+
<a href="/" class="nav-brand"> <span class="prompt">$</span> aimock </a>
29+
</div>
30+
<ul class="nav-links">
31+
<li><a href="/">Home</a></li>
32+
<li><a href="/docs" style="color: var(--accent)">Docs</a></li>
33+
<li>
34+
<a href="https://github.com/CopilotKit/aimock" class="gh-link" target="_blank">
35+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
36+
<path
37+
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"
38+
/>
39+
</svg>
40+
GitHub
41+
</a>
42+
</li>
43+
</ul>
44+
</div>
45+
</nav>
46+
47+
<div class="docs-layout">
48+
<aside class="sidebar" id="sidebar"></aside>
49+
50+
<main class="docs-content">
51+
<h1>fal.ai</h1>
52+
<p class="lead">
53+
aimock mocks the fal.ai inference API &mdash; queue-based and synchronous runs for image,
54+
video, audio, and any other fal model. Routes by <code>x-fal-target-host</code> header to
55+
mirror the <code>@fal-ai/client</code> proxy convention.
56+
</p>
57+
58+
<h2>How It Works</h2>
59+
<p>
60+
The <code>@fal-ai/client</code> SDK routes requests through a proxy using the
61+
<code>x-fal-target-host</code> header to indicate the upstream fal service:
62+
</p>
63+
<ul>
64+
<li>
65+
<code>queue.fal.run</code> &mdash; queue-based operations (submit, poll status, fetch
66+
result)
67+
</li>
68+
<li><code>fal.run</code> &mdash; synchronous single-shot runs</li>
69+
<li><code>rest.fal.ai</code> &mdash; storage and other REST endpoints</li>
70+
</ul>
71+
<p>
72+
aimock intercepts <code>POST /fal/{owner}/{model}</code> requests bearing this header and
73+
handles the full queue lifecycle: it auto-mints a <code>request_id</code>, returns status
74+
and response URLs, and serves the matched fixture payload on result fetch.
75+
</p>
76+
<p>
77+
Legacy path-based routing is also supported &mdash;
78+
<code>/fal/queue/submit/{model}</code>, <code>/fal/queue/requests/{id}</code>, and
79+
<code>/fal/run/{model}</code> all continue to work for backward compatibility.
80+
</p>
81+
82+
<h2>Quick Start (Programmatic)</h2>
83+
84+
<div class="code-block">
85+
<div class="code-block-header">fal-mock.test.ts <span class="lang-tag">ts</span></div>
86+
<pre><code><span class="kw">import</span> { <span class="type">LLMock</span> } <span class="kw">from</span> <span class="str">"@copilotkit/aimock"</span>;
87+
<span class="kw">import</span> { <span class="fn">describe</span>, <span class="fn">it</span>, <span class="fn">expect</span>, <span class="fn">beforeAll</span>, <span class="fn">afterAll</span> } <span class="kw">from</span> <span class="str">"vitest"</span>;
88+
89+
<span class="kw">let</span> <span class="op">mock</span>: <span class="type">LLMock</span>;
90+
91+
<span class="fn">beforeAll</span>(<span class="kw">async</span> () <span class="kw">=&gt;</span> {
92+
<span class="op">mock</span> = <span class="kw">new</span> <span class="type">LLMock</span>();
93+
<span class="kw">await</span> <span class="op">mock</span>.<span class="fn">start</span>();
94+
});
95+
96+
<span class="fn">afterAll</span>(<span class="kw">async</span> () <span class="kw">=&gt;</span> {
97+
<span class="kw">await</span> <span class="op">mock</span>.<span class="fn">stop</span>();
98+
});
99+
100+
<span class="fn">it</span>(<span class="str">"image generation via queue"</span>, <span class="kw">async</span> () <span class="kw">=&gt;</span> {
101+
<span class="cm">// Register a queue fixture for Flux image generation</span>
102+
<span class="op">mock</span>.<span class="fn">onFalQueue</span>(<span class="str">/flux/</span>, { <span class="prop">images</span>: [{ <span class="prop">url</span>: <span class="str">"https://example.com/cat.png"</span> }] });
103+
104+
<span class="cm">// Submit &rarr; status &rarr; result, just like the real API</span>
105+
});
106+
107+
<span class="fn">it</span>(<span class="str">"video generation via queue"</span>, <span class="kw">async</span> () <span class="kw">=&gt;</span> {
108+
<span class="op">mock</span>.<span class="fn">onFalQueue</span>(<span class="str">/kling/</span>, { <span class="prop">video</span>: { <span class="prop">url</span>: <span class="str">"https://example.com/v.mp4"</span> } });
109+
});
110+
111+
<span class="fn">it</span>(<span class="str">"synchronous transcription"</span>, <span class="kw">async</span> () <span class="kw">=&gt;</span> {
112+
<span class="cm">// Sync runs skip the queue entirely</span>
113+
<span class="op">mock</span>.<span class="fn">onFalRun</span>(<span class="str">/whisper/</span>, { <span class="prop">text</span>: <span class="str">"Hello world"</span> });
114+
});</code></pre>
115+
</div>
116+
117+
<h2>Client Configuration</h2>
118+
<p>
119+
Point the <code>@fal-ai/client</code> at aimock using <code>requestMiddleware</code> to
120+
rewrite the proxy URL:
121+
</p>
122+
123+
<div class="code-block">
124+
<div class="code-block-header">fal-client-config.ts <span class="lang-tag">ts</span></div>
125+
<pre><code><span class="kw">import</span> { <span class="op">fal</span> } <span class="kw">from</span> <span class="str">"@fal-ai/client"</span>;
126+
127+
<span class="op">fal</span>.<span class="fn">config</span>({
128+
<span class="prop">requestMiddleware</span>: <span class="op">fal</span>.<span class="fn">withMiddleware</span>(
129+
<span class="op">fal</span>.<span class="fn">withProxy</span>({
130+
<span class="prop">targetUrl</span>: <span class="str">"http://localhost:4005/fal"</span>, <span class="cm">// aimock default port</span>
131+
})
132+
),
133+
});</code></pre>
134+
</div>
135+
136+
<div class="info-box">
137+
<p>
138+
The client sends the original target host (e.g. <code>queue.fal.run</code>) in the
139+
<code>x-fal-target-host</code> header. aimock reads this header to decide whether to
140+
handle the request as a queue operation, a sync run, or a storage call.
141+
</p>
142+
</div>
143+
144+
<h2>Queue Lifecycle</h2>
145+
<p>
146+
Queue-based operations follow a four-step lifecycle. aimock handles all steps
147+
automatically once a fixture is registered:
148+
</p>
149+
150+
<table class="endpoint-table">
151+
<thead>
152+
<tr>
153+
<th>Step</th>
154+
<th>Method</th>
155+
<th>Path</th>
156+
<th>Response</th>
157+
</tr>
158+
</thead>
159+
<tbody>
160+
<tr>
161+
<td>Submit</td>
162+
<td>POST</td>
163+
<td><code>/fal/{owner}/{model}</code></td>
164+
<td>
165+
<code>{ request_id, status_url, response_url, cancel_url }</code>
166+
</td>
167+
</tr>
168+
<tr>
169+
<td>Status</td>
170+
<td>GET</td>
171+
<td><code>/fal/{owner}/{model}/requests/{id}/status</code></td>
172+
<td><code>{ status: "COMPLETED" }</code></td>
173+
</tr>
174+
<tr>
175+
<td>Result</td>
176+
<td>GET</td>
177+
<td><code>/fal/{owner}/{model}/requests/{id}</code></td>
178+
<td>The matched fixture payload</td>
179+
</tr>
180+
<tr>
181+
<td>Cancel</td>
182+
<td>PUT</td>
183+
<td><code>/fal/{owner}/{model}/requests/{id}/cancel</code></td>
184+
<td><code>{ status: "ALREADY_COMPLETED" }</code> (400)</td>
185+
</tr>
186+
</tbody>
187+
</table>
188+
189+
<h2>JSON Fixture File</h2>
190+
191+
<div class="code-block">
192+
<div class="code-block-header">fixtures/fal.json <span class="lang-tag">json</span></div>
193+
<pre><code>{
194+
<span class="key">"fixtures"</span>: [
195+
{
196+
<span class="key">"match"</span>: { <span class="key">"model"</span>: <span class="str">"fal-ai/flux/dev"</span>, <span class="key">"endpoint"</span>: <span class="str">"fal"</span> },
197+
<span class="key">"response"</span>: {
198+
<span class="key">"json"</span>: {
199+
<span class="key">"images"</span>: [{ <span class="key">"url"</span>: <span class="str">"https://example.com/result.png"</span> }]
200+
}
201+
}
202+
}
203+
]
204+
}</code></pre>
205+
</div>
206+
207+
<h2>Record &amp; Replay</h2>
208+
<p>
209+
Use <code>--record</code> with the <code>providers.fal</code> configuration to capture
210+
real fal.ai responses and replay them in tests:
211+
</p>
212+
213+
<div class="code-block">
214+
<div class="code-block-header">CLI <span class="lang-tag">sh</span></div>
215+
<pre><code><span class="fn">npx</span> aimock <span class="op">--record</span> <span class="op">--fixtures</span> fixtures/fal.json</code></pre>
216+
</div>
217+
218+
<p>
219+
When recording, the <code>x-fal-target-host</code> header is used to resolve the upstream
220+
fal service automatically &mdash; no additional provider configuration is needed.
221+
Responses are saved as fixtures that can be replayed without network access.
222+
</p>
223+
224+
<h2>Legacy Routes</h2>
225+
<p>
226+
For backward compatibility, aimock also supports the older path-based routing convention
227+
used by the audio-specific handler (<code>fal-audio.ts</code>):
228+
</p>
229+
<ul>
230+
<li><code>POST /fal/queue/submit/{model}</code> &mdash; submit a queue job</li>
231+
<li><code>GET /fal/queue/requests/{id}</code> &mdash; fetch the result</li>
232+
<li><code>POST /fal/run/{model}</code> &mdash; synchronous run</li>
233+
</ul>
234+
<p>
235+
These paths work identically to the header-routed equivalents and share the same fixture
236+
matching logic.
237+
</p>
238+
</main>
239+
<aside class="page-toc" id="page-toc"></aside>
240+
</div>
241+
242+
<footer class="docs-footer">
243+
<div class="footer-inner">
244+
<div class="footer-left"><span>$</span> aimock &middot; MIT License</div>
245+
<ul class="footer-links">
246+
<li><a href="https://github.com/CopilotKit/aimock" target="_blank">GitHub</a></li>
247+
<li>
248+
<a href="https://www.npmjs.com/package/@copilotkit/aimock" target="_blank">npm</a>
249+
</li>
250+
</ul>
251+
</div>
252+
</footer>
253+
<script src="../sidebar.js"></script>
254+
<script src="../cli-tabs.js"></script>
255+
</body>
256+
</html>

docs/sidebar.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
{ label: "Text-to-Speech", href: "/speech" },
3737
{ label: "Audio Transcription", href: "/transcription" },
3838
{ label: "Video Generation", href: "/video" },
39+
{ label: "fal.ai", href: "/fal-ai" },
3940
],
4041
},
4142
{

0 commit comments

Comments
 (0)