Skip to content

Add fal.ai as a first-class provider (record + replay) #152

@tombeckenham

Description

@tombeckenham

Summary

aimock already has rich support for OpenAI / Gemini / Anthropic / Bedrock / Cohere / Ollama formats (including images, audio, video). It would be a great fit to also speak fal.ai's API — both for sync (https://fal.run/{owner}/{model}) and queue (https://queue.fal.run/...) endpoints, plus storage (rest.fal.ai/rest.alpha.fal.ai).

Adding fal as a RecordProviderKey would let teams using @fal-ai/client directly or @tanstack/ai-fal route their AI traffic through aimock the same way they do for LLM providers today, instead of having to mount a custom handler.

Use case

We're building an e2e test for a video generation app that calls fal.ai for image/motion/music generation through @tanstack/ai-fal. We already use aimock for OpenRouter — having fal in the same record-and-replay system would mean a single fixtures directory, a single port, and consistent ergonomics across providers.

For now we mount a custom fal handler via LLMock.mount('/fal', ...) and have the app point fal-client at http://localhost:4010/fal via requestMiddleware. Working sketch (record/replay, body-hash keyed):

// e2e/mocks/fal-handler.ts (sketch)
const FAL_HOSTS = new Set([
  'fal.run',
  'queue.fal.run',
  'rest.fal.ai',
  'rest.alpha.fal.ai',
  'gateway.fal.ai',
]);

// Server-side fal-client doesn't honor proxyUrl (browser-only middleware),
// so we install requestMiddleware that rewrites the URL and forwards the
// original host as a header.
fal.config({
  requestMiddleware: async (req) => {
    const original = new URL(req.url);
    if (!FAL_HOSTS.has(original.hostname)) return req;
    const rewritten = new URL(process.env.FAL_PROXY_URL!);
    rewritten.pathname = (rewritten.pathname.replace(/\/\$/, '')) + original.pathname;
    rewritten.search = original.search;
    return {
      ...req,
      url: rewritten.toString(),
      headers: { ...(req.headers ?? {}), 'x-fal-target-host': original.hostname },
    };
  },
});

This works but it'd be much cleaner as a built-in.

Surface to support

Auth is `Authorization: Key {FAL_KEY}`.

Suggested API

Mirror the existing format options:

new LLMock({
  record: {
    providers: {
      openai: 'https://openrouter.ai/api/v1',
      fal: 'https://fal.run', // or omit upstream URL — it's in the request hostname
    },
    fixturePath: './fixtures',
  },
});

mock.onFalRun(/flux/, { images: [{ url: '...' }] });
mock.onFalQueue(/kling/, { request_id: '...', /* ... */ });

For async/queue calls aimock would need to keep a small in-memory state map per request_id (similar to what `video.ts` already does for video-state).

Forwarding pattern

When proxying server-side, fal-client doesn't natively support `proxyUrl` (`withProxy` no-ops outside the browser — see middleware.ts). The `x-fal-target-host` request middleware pattern (sketch above) is the simplest workaround we found, and aimock could document/enable it directly.

Happy to help

We have a working handler already. If a maintainer is open to it, I can put up a PR that adds `src/fal.ts` alongside the existing provider modules — happy to follow whatever conventions you'd prefer for fixture format / handler shape.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions