Raw Passthrough Fixtures
++ Send provider events exactly as you write them. A raw fixture lists the events (streaming) + or the JSON body (non-streaming) of one provider wire, and aimock adds only that wire's + framing. Use it to test an SDK against a new event type, field or error shape before + aimock models it. +
+ +
+ Prefer typed fixtures. content, toolCalls,
+ blocks and the other typed responses work on every provider, get usage,
+ reasoning and override handling, and are checked for drift against the real APIs. Raw
+ fixtures are wire-specific and are not checked for drift: when the
+ provider changes, your raw events do not. Use raw only for what typed fixtures cannot
+ express yet.
+
Shape
+{
+ "fixtures": [
+ {
+ "match": { "userMessage": "cite paris" },
+ "response": {
+ "raw": {
+ "wire": "openai-responses",
+ "events": [
+ { "type": "response.created", "response": { "id": "resp_1", "status": "in_progress", "output": [] } },
+ { "type": "response.output_text.delta", "delta": "Paris.", "output_index": 0, "content_index": 0 },
+ { "type": "response.output_text.annotation.added", "annotation": { "type": "url_citation", "url": "https://example.com" } },
+ { "type": "response.completed", "response": { "id": "resp_1", "status": "completed", "output": [] } }
+ ],
+ "json": { "id": "resp_1", "object": "response", "status": "completed", "output": [] },
+ "headers": { "x-request-id": "req_123" }
+ }
+ }
+ }
+ ]
+}
+ | Field | +Type | +Description | +
|---|---|---|
wire |
+ string, required | ++ The wire the events belong to (see the framing table). A raw fixture matches only + requests on that wire; every other handler skips it and keeps looking. A factory + is the exception: its result is checked when it runs, and a raw result for another + wire gets a 500 (see below). + | +
events |
+ object[] | +
+ Streamed body. Each entry is sent as one frame, framed per wire.
+ |
+
json |
+ any | +Non-streaming body, sent as JSON.stringify(json). |
+
status |
+ integer 200-599 | +
+ HTTP status for both streaming and non-streaming replies. Default 200; 204 and 304
+ are rejected (they cannot carry a body). A reply with status 400 or above is never
+ delayed. A 4xx/5xx with json sends that JSON error body as written,
+ even to a streaming request.
+ |
+
headers |
+ object of strings | +
+ Extra response headers, for example x-request-id or
+ anthropic-ratelimit-*. Unless you set Content-Type here,
+ a stream gets the same default headers as a typed stream on its wire
+ (text/event-stream, application/x-ndjson on Ollama,
+ application/vnd.amazon.eventstream on Bedrock) and a non-streaming
+ reply gets application/json on every wire. A header you set replaces
+ the default of the same name in any letter case, so it is sent once.
+ content-length, transfer-encoding,
+ connection and content-encoding are rejected.
+ |
+
done |
+ boolean | +
+ openai-chat only: send the terminal data: [DONE].
+ Default true. It must be a boolean; any other wire rejects it.
+ |
+
+ raw is recognized only when the response has no other type's key. A fixture
+ such as { "content": "hi", "raw": { … } } stays a text fixture, as before.
+ The exceptions are customToolCalls and responsesBlocks: a
+ fixture that writes either next to raw is rejected. A key inside
+ raw that is not one of the fields above is ignored with a warning.
+
Framing per wire
+wire |
+ Endpoints | +Each event is sent as | +
|---|---|---|
openai-responses |
+ POST /v1/responses |
+ event: <type>\ndata: <json>\n\n (no terminal) |
+
openai-chat |
+ POST /v1/chat/completions (also Azure and OpenRouter routes) |
+ data: <json>\n\n, then data: [DONE]\n\n |
+
anthropic |
+ POST /v1/messages |
+ event: <type>\ndata: <json>\n\n |
+
gemini |
+
+ generateContent, streamGenerateContent (also Vertex AI)
+ |
+ data: <json>\n\n |
+
gemini-interactions |
+ POST /v1beta/interactions |
+ data: <json>\n\n |
+
cohere |
+ POST /v2/chat |
+ event: <type>\ndata: <json>\n\n (no terminal) |
+
ollama |
+ POST /api/chat |
+ <json>\n (NDJSON) |
+
ollama-generate |
+ POST /api/generate |
+ <json>\n (NDJSON) |
+
bedrock-invoke |
+ /invoke, /invoke-with-response-stream |
+ An AWS event-stream chunk frame with base64 bytes |
+
bedrock-converse |
+ /converse, /converse-stream |
+
+ An AWS event-stream frame: the event's single key is the event type, its value the
+ payload ({ "contentBlockDelta": { … } })
+ |
+
+ On openai-responses, anthropic and cohere every
+ event needs a string type; it names the SSE event. Each raw event is one
+ frame on every wire, including Gemini Interactions (where typed fixtures split text by
+ chunkSize).
+
Streaming and non-streaming requests
+
+ A streaming request gets events; a non-streaming request gets
+ json. The exception is an error reply: with a status of 400 or
+ above and a json, a streaming request also gets the JSON, as provider APIs
+ answer errors. If the one the request needs is missing, aimock answers with that wire's
+ native 500 error and the code aimock_raw_mode_unavailable. An invalid raw
+ fixture (for example a factory that returns status: 99) gets the same 500
+ with the code aimock_raw_fixture_invalid, and so does a factory that returns
+ a raw fixture for another wire. A raw fixture added as an object for another wire is never
+ served there: the router skips it, and when no other fixture matches, the server log warns
+ that a raw fixture matched everything except its wire. The error body uses each provider's
+ own error shape (on Gemini, Ollama and OpenRouter the code is prefixed to the message).
+
+ In an OpenRouter models fallback chain, an openai-chat raw
+ fixture with a status of 400 or above fails over to the next model, as a
+ typed error fixture with the same status does.
+
Timing
+
+ latency, streamingProfile, recordedTimings and
+ replaySpeed pace streamed raw events exactly as they pace typed frames (see
+ Streaming Physics).
+
+ A non-streaming raw reply follows nonStreamingTiming like a typed fixture:
+ nonStreamingTiming: true delays a raw stream: false reply on
+ OpenAI Chat Completions only, and nonStreamingTimingAllProviders: true
+ delays it on every raw wire (including OpenAI Chat Completions). Either flag needs a
+ streamingProfile or recordedTimings; a bare
+ latency never delays a non-streaming reply. The wait is sized from the served
+ JSON, as a typed reply's is sized from its content. A raw reply with
+ status 400 or above is never delayed, streaming or not.
+
Which features apply
+| Feature | +Raw fixtures | +
|---|---|
+ Matching (all match keys, sequenceIndex, turnIndex,
+ predicate, factories)
+ |
+ Yes. Plus the wire filter above. | +
truncateAfterChunks, disconnectAfterMs, chaos |
+ Yes. Every raw event counts as one chunk. | +
| Streaming timing | +Yes (see Timing). | +
chunkSize |
+ No. Each event is one frame. Setting it warns at load. | +
| Model misbehavior | +
+ No: faults skip raw fixtures. A server-level fault is skipped, the raw reply is
+ served, and the journal records the fault as skipped. An
+ X-AIMock-Misbehavior header returns 501
+ aimock_misbehavior_not_applicable. A raw fixture that sets its own
+ misbehavior is an invalid fixture: load validation reports it (with
+ --misbehavior, its file fails to load), and when served it gets the
+ wire's 500 with aimock_raw_fixture_invalid.
+ |
+
| Overrides, reasoning gating, usage synthesis, drift checks | +No. The events are sent as written. | +
| Record mode | +A matched raw fixture never proxies. Recording raw fixtures is a follow-up. | +
Validation
+
+ --validate-on-load, addFixturesFromJSON and
+ POST /__aimock/fixtures check raw fixtures the same way: a known
+ wire, events or json present, events that are plain
+ JSON objects (no sparse-array holes, undefined, non-finite numbers,
+ functions, Dates or cycles), the per-wire type / single-key rules (no CR or
+ LF in a type; a bedrock-converse key of at most 65535 bytes),
+ status 200-599 except 204 and 304, valid header names and values, a boolean
+ done only on openai-chat, no customToolCalls or
+ responsesBlocks next to raw, and no fixture
+ misbehavior. Unknown keys inside raw and an empty
+ events are warnings. on(), addFixture() and
+ factories are not validated when added; an invalid raw fixture registered that way returns
+ the wire's 500 at request time. The same check runs again on every raw fixture when it is
+ served.
+
Programmatic use
+const mock = new LLMock();
+mock.on({ userMessage: "weather" }, {
+ raw: {
+ wire: "anthropic",
+ events: [
+ { type: "message_start", message: { /* … */ } },
+ { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "Sunny." } },
+ { type: "message_stop" },
+ ],
+ },
+});
+ Limits
+-
+
-
+ WebSocket Responses (
/v1/responsesover WebSocket) answers a raw fixture + with an error event with the codeaimock_raw_mode_unavailable: "raw + fixtures are not yet supported on the WebSocket transport". Serving raw over WebSocket + is a follow-up. +
+ - + Realtime, Gemini Live and non-LLM endpoints (embeddings, images, audio, video) never + match raw fixtures. For a transport aimock does not cover, use + mount(). + +