# @copilotkit/aimock

## [1.36.1] - 2026-07-14

### Fixed

- `aimock.json` now honours `llm.latency`, `llm.chunkSize`, `llm.replaySpeed` and `llm.logLevel`, which the config loader previously accepted and ignored. Each behaves as its `llmock` CLI flag and `createMockSuite({ llm })` equivalent. `llm.replaySpeed` divides every delay source (recorded timings, streaming profiles, `latency`), so a fixture suite can replay recorded inter-chunk timings faster than they were recorded; a fixture's own `replaySpeed` still takes precedence. A non-positive `llm.replaySpeed` is ignored with a warning, matching the existing fixture-level guard (#294)

## [1.36.0] - 2026-07-13

### Added

- On a fixture-miss passthrough (record/proxy mode), aimock can inject its own configured upstream provider key instead of forwarding a caller's dummy placeholder key. Every static-key provider aimock proxies is wired end-to-end, each with an independent `AIMOCK_PROVIDER_<PROVIDER>_KEY` env var applied with the provider-correct wire scheme: `Authorization: Bearer` for OpenAI (`_OPENAI_KEY`), OpenRouter (`_OPENROUTER_KEY`), Cohere (`_COHERE_KEY`), Grok/xAI (`_GROK_KEY`), and Ollama (`_OLLAMA_KEY`); `x-api-key` for Anthropic (`_ANTHROPIC_KEY`); `x-goog-api-key` for Gemini (`_GEMINI_KEY`, also used for Gemini Interactions) and Veo (`_VEO_KEY`); `api-key` for Azure OpenAI (`_AZURE_KEY`); `xi-api-key` for ElevenLabs (`_ELEVENLABS_KEY`); and `Authorization: Key` for fal.ai (`_FAL_KEY`). Injection fires only when the caller credential is absent or dummy-prefixed (`sk-aimock-`, overridable via `AIMOCK_DUMMY_KEY_MARKER`); a real caller key is always forwarded unchanged, an empty-string env var is treated as unset, and with no built-in key configured the feature is inert. Signed/exchanged credentials — AWS Bedrock (SigV4) and Vertex AI (OAuth) — are never rewritten (#293)

## [1.35.1] - 2026-07-06

### Fixed

- Record mode no longer silently drops fixtures when an SDK closes its socket immediately after `data: [DONE]` (e.g. the OpenAI Python SDK); the completed upstream response is now persisted. Genuine mid-stream aborts (client disconnect before `[DONE]`) still abort upstream and write no fixture (#288)

## [1.35.0] - 2026-06-27

### Added

- Native Google Veo async video lifecycle mock — `POST /v1beta/models/{model}:predictLongRunning` submit, `GET /v1beta/operations/{name}` poll through `done:false → done:true`, poll-count progression via `veoVideo`; the Files-API `uri` is served as-is (aimock never proxies or downloads video bytes) (#278)
- Record-mode live proxying for the Veo surface (`record.providers.veo`) — submit and poll forwarded 1:1, eager fixture capture of the Files-API uri on `done:true`; captured operations replay later (#278)
- Native xAI Grok Imagine async video lifecycle mock — `POST /v1/videos/generations` submit (JSON-only; multipart rejected with 400), `GET /v1/videos/{request_id}` poll through `pending → done | failed | expired` with synthesized `progress`, `grokVideo` progression, `cost_in_usd_ticks` units, and a Sora-safe `/v1/videos/{id}` dispatch that leaves the OpenAI video surface unchanged (#278)
- Record-mode live proxying for the Grok surface (`record.providers.grok`) — submit and poll forwarded 1:1, eager fixture capture of url/duration/cost on `done`, `failed` persisted, `expired` passed through; captured jobs replay later (#278)
- Optional `blocks` array on the combined `content` + `toolCalls` fixture shape lets a fixture express ordered text/tool-call blocks (`{type:"text",text}` | `{type:"toolCall",name,arguments,id?}`); when present it takes precedence over `{content, toolCalls}` for stream order, enabling tool-first and interleaved ordering. Legacy `{content, toolCalls}` fixtures are unchanged (#274)
- All five providers stream combined responses in fixture block order: Anthropic, OpenAI Responses, and Gemini are fully observable; Ollama is best-effort (clients may reassemble positionally); OpenAI chat-completions emits in order but is degenerate (`delta.content`/`delta.tool_calls` are separate channels the client merges) (#274)
- Recorder captures block order and persists `blocks` only when the recorded upstream stream was genuinely tool-first or interleaved; text-first streams keep the legacy `{content, toolCalls}` shape so golden recordings round-trip byte-identically (#274)
- Blocks-only fixtures are first-class: a non-empty `blocks` array is a complete response shape on its own, with no `content`/`toolCalls` required — builders derive the aggregate from the blocks and `validateFixtures()` accepts the shape (#274)
- Block ordering is now honored on replay across the remaining providers — Cohere (streaming), Bedrock invoke, Bedrock Converse, and Gemini Interactions — so a tool-first or interleaved fixture streams its tool call ahead of its text wherever the wire protocol can express it (#274)
- Record-side block capture extends to the Cohere and Bedrock collapsers; Gemini Interactions normalizes tool-call arguments only and does not reorder blocks on capture (its step-index protocol can't reconcile arrival-order blocks at record time), while replay still honors a hand-authored `blocks` array (#274)
- `validateBlocks` rejects a malformed `blocks` array at load time — non-array, non-object entries, a `type` other than `text`/`toolCall`, a non-string or empty-string text block, or a `toolCall` block missing a name or carrying non-JSON arguments — and warns when a fixture carries both `blocks` and divergent `content`/`toolCalls`, so a bad array never reaches a builder mid-dispatch (#274)

## [1.34.0] - 2026-06-24

### Changed

- Replay matching is content-anchored: `turnIndex` disambiguates, no longer a hard reject gate (#276)
- Empirical over 9769 real requests: 3213 false-miss fixes, 0 new misses, 0 wrong-fixture (#276)
- Diverges only on off-by-N assistant count in either direction (behind OR ahead of turn) (#276)
- New `turnIndexRelaxed` match diagnostic + one-shot logger warn on a divergent relaxed serve (#276)
- `AIMOCK_STRICT_TURN_INDEX=1` restores the legacy strict turnIndex gate for replay (#276)

### Fixed

- Gemini Interactions mock now emits the SDK 2.x event protocol on both paths — streamed SSE (`step.*`, `interaction.created`/`completed`, tool args via `arguments_delta`) and non-streaming responses (`steps`/`output_text`); legacy 1.x recorded fixtures still parse (#279)

## [1.33.0] - 2026-06-23

### Added

- `--max-proxy-buffer-bytes` / `--max-proxy-buffer-frames` flags to cap proxy buffering (#275)

### Fixed

- Proxy path no longer leaks memory on long-lived upstream streams (#275)
- Oversized proxied responses no longer crash with `Invalid string length` (#275)
- Sanitize `X-AIMock-Context` to prevent fixture path traversal (#272)
- Scope one-shot error injection, improve recorder fixture fidelity, fix `matchesPattern` lastIndex (#272)

## [1.32.0] - 2026-06-22

### Added

- fal queue `status`/`result` responses now emit `x-fal-request-id`, and a `billableUnits` fixture field (e.g. `onFalQueue(model, json, { billableUnits })`) emits `x-fal-billable-units` on the completed result so adapters like `@tanstack/ai-fal` can surface `usage.unitsBilled` on replay. Record mode captures the upstream `x-fal-billable-units` header automatically, so recorded fixtures round-trip billing with no hand-editing (#269)

## [1.31.0] - 2026-06-10

### Added

- OpenRouter async video job lifecycle mock — submit, poll, content download, model listing (#262)
- Record-mode live proxying for the OpenRouter video surface; captured videos replay later (#265)

### Changed

- Recording proxies now strip aimock-internal control headers on every provider path (#265)

### Fixed

- Recorder and fal record paths hardened — timeouts, threshold sanitizing, persist errors (#265)
- attw ^0.18 fixes the test:exports crash (#263); OpenAI /v1/videos docs corrected (#264)

## [1.30.0] - 2026-06-09

### Added

- **Extended-thinking request invariants** — aimock now validates Anthropic extended-thinking continuations on the tool-use loop. When extended thinking is enabled, a continuation whose prior assistant turn drops the leading `thinking` block (or its `signature`, or a `redacted_thinking` block's `data`) is rejected with the real Anthropic `400`, instead of producing a false-green replay — under strict mode; otherwise the violation warns and replay proceeds. Emitted thinking blocks now carry a non-empty placeholder signature so record→replay round-trips stay green across text, content+tool, and tool-only response shapes.
- **Faithful reasoning capture + replay** — the recorder now preserves provider reasoning artifacts that recording previously dropped. Anthropic thinking-block signatures (the `signature_delta` wire event on the streaming paths; each thinking block's `signature` field non-streaming) and `redacted_thinking` block `data` are captured across all three Anthropic capture paths — the SSE stream collapser, the Anthropic-native binary frames on Bedrock invoke-with-response-stream (`thinking_delta`/`signature_delta`/`redacted_thinking`), and the non-streaming JSON recording path. Bedrock Converse `reasoningContent` deltas and Cohere `thinking` content-deltas are captured into reasoning, and a recorded Anthropic `reasoningSignature` plus any `redactedThinking` payloads are written into the saved fixture (`TextResponse` / `ToolCallResponse` / `ContentWithToolCallsResponse`). On replay, Anthropic emits the recorded signature when one was captured and otherwise falls back to the round-trip-safe placeholder, and recorded `redactedThinking` payloads are replayed as faithful leading `redacted_thinking` content blocks (streaming `content_block_start`/`content_block_stop` with the opaque `data`, non-streaming content-array blocks), across both streaming and non-streaming text, content+tool, and tool-only response shapes. The encrypted reasoning artifacts (`reasoningSignature` and `redactedThinking`) are gated on the same model-capability resolution as the plaintext reasoning channel, so replaying a reasoning-model fixture against a non-reasoning model under strict mode suppresses the `thinking`, `signature`, and `redacted_thinking` channels together rather than leaving the encrypted blocks half-gated. Fidelity caveat: the interleaving of `thinking` and `redacted_thinking` blocks and per-block signatures are not preserved — all recorded `redacted_thinking` blocks replay as a leading group, and the merged thinking block carries the `signature` of the last thinking block that actually emitted one (a signature-less block does not clobber an earlier signature).

### Changed

- **Reasoning emission** — replaying a reasoning channel is now gated on the requested model's capability. aimock no longer synthesizes a reasoning channel (chat `reasoning_content` / Responses `reasoning_summary_text` / Anthropic thinking / etc.) for models that would not emit reasoning against the real provider. A new `isReasoningModel` classifier and `resolveReasoningForModel` gate are applied across OpenAI chat + Responses, Anthropic, Ollama, Gemini, Cohere, Bedrock (invoke + Converse), and WebSocket Responses: a non-reasoning model paired with a reasoning fixture has its reasoning suppressed under strict mode, or warns-and-emits otherwise. The `AIMOCK_REASONING_MODELS` and `AIMOCK_NONREASONING_MODELS` env vars override the classifier.
- **Tool-only reasoning emission** — the capability gate now also covers the tool-call-only response path of every provider (OpenAI chat + Responses, Cohere, Bedrock invoke + Converse, Ollama, WebSocket Responses, and the Gemini chat tool-only path). Previously a tool-only fixture carrying reasoning dropped the reasoning channel entirely; it now emits the provider-native reasoning channel for reasoning-capable models and is suppressed under strict mode (or warns-and-emits otherwise), matching the text and content+tool paths, with leading reasoning blocks shifting streaming tool/output indices by one where applicable. The Gemini audio companion's reasoning is now capability-gated as well.
- Strict-mode 503 now distinguishes sequence/turn exhaustion from a true no-match: when candidate fixtures matched the request shape but were skipped by `sequenceIndex`/`turnIndex` count state, the 503 message and error log read `N candidate fixture(s) skipped by sequence/turn state` instead of the generic `no fixture matched`. HTTP status (503) and error envelope shape are unchanged. Applied across all strict-mode emission sites via a shared helper. Design notes: the skip diagnostic is captured from the same single matcher pass that performs the match — stateful `match.predicate` functions fire exactly once per request — and the `hasToolResult` shape predicate is evaluated before the `sequenceIndex`/`turnIndex` state gates, so the skip count only includes fixtures whose request shape actually matched.

### Fixed

- **Recorder** — pathological-but-real Anthropic turns (thinking-only with empty text, redacted-only, redacted blocks with empty `data`) now record as normal empty-content fixtures instead of "Could not detect response format" error fixtures, with streaming/non-streaming parity.
- `redacted_thinking` blocks with empty `data` are filtered at every capture site via one shared predicate, matching the strict replay validator — a recorded fixture can no longer 400 on replay for data the validator rejects (record-green ⇒ replay-green).

## [1.29.0] - 2026-06-04

### Added

- `POST /__aimock/reset/fixtures` — full reset (clears fixtures, generation state, and journal).
- `POST /__aimock/reset/journal` — clears only the request journal, leaving fixtures intact.
- `aimock-pytest`: `reset_fixtures()` and `reset_journal()` client methods.
- Control API reference documentation.

### Changed

- `engines.node` lowered to `>=20.15.0`. The runtime supports Node 20 (the published CLI runs on Node 20/22/24); the previous `>=24.0.0` floor reflected an OIDC publish-time requirement, which is a CI concern handled by the publish workflow, not a runtime constraint for consumers.

### Deprecated

- `POST /__aimock/reset` — now a deprecated alias for `/__aimock/reset/fixtures`; it still performs a full reset but emits a `Deprecation` response header and a `deprecated` field in the body. Use the explicit `/reset/fixtures` or `/reset/journal` routes instead.

### Fixed

- **Video** — `POST /v1/videos` (`videos.create`) now parses `multipart/form-data` bodies. The OpenAI SDK (>=6.28.0) sends video-create requests as multipart instead of JSON (even for File-less bodies), which previously returned a `400 invalid_json`. The handler reuses the existing transcription multipart field parser and preserves the JSON path for older SDKs.
- `aimock-pytest`: per-fixture options passed via `add_fixture`/`on_message`/etc. (`latency`, `chunkSize`, `sequenceIndex`, `chaos`, …) are now forwarded in the correct wire shape. They were previously nested under an `opts` key the server ignored, so they were silently dropped.
- `aimock-pytest`: `_wait_for_ready` now honors its startup timeout (a background reader thread drains the child's stdout, so a no-output child can no longer hang past the deadline or deadlock on a full pipe buffer), tears down the subprocess on a startup failure, and surfaces the child's captured output (the health-check failure path also reports accurate elapsed time).
- `DELETE /v1/_requests` now clears only the request-journal entries, preserving fixture match-counts (so sequenced fixtures are not rewound) — consistent with `POST /__aimock/reset/journal`.

## [1.28.0] - 2026-06-02

### Added

- **Harmony channel format** — parse OpenAI "harmony" channel tokens (`<|channel|>… <|message|>… <|call|>`) emitted by local gpt-oss models (Ollama / vLLM / OpenRouter) so their tool calls, reasoning, and content are captured when recording (hosted OpenAI pre-parses harmony, so only local runtimes pass it through raw). Implemented as a lexer + state-machine parser with a uniform all-or-nothing verbatim fail-safe, wired as fallback-only so it never produces phantom tool calls.

### Fixed

- **Recorder** — decode streamed response chunks incrementally to prevent multibyte UTF-8 corruption; CRLF-tolerant frame-timing splitter; propagate `webSearches` and audio-companion fields (tool calls / content / reasoning) into recorded fixtures; log `firstDroppedSample` alongside dropped-chunk warnings.
- **Stream collapsers** — multi-line and CRLF SSE handling; missing/uncorrelated tool-call index guards with symmetric dropped-chunk accounting across OpenAI / Anthropic / Bedrock / Cohere; bound Bedrock EventStream header parsing against malformed frames.
- **Gemini** — replay audio-companion tool calls / content / reasoning on audio turns instead of dropping them.

## [1.27.3] - 2026-05-27

### Fixed

- correct OpenAI images endpoint path from `/v1/images/edit` to `/v1/images/edits` (closes [#221](https://github.com/CopilotKit/aimock/issues/221))
- add Ollama `/api/embed` route as alias for `/api/embeddings`; `/api/embed` is the current documented endpoint ([ollama/ollama docs/api.md](https://github.com/ollama/ollama/blob/main/docs/api.md)) used by modern Ollama SDKs, while `/api/embeddings` is retained for backwards-compatibility. Both routes dispatch to the same handler.

## [1.27.2] - 2026-05-26

### Fixed

- **Fixture loader** — full recursive directory traversal replaces 2-level cap; supports per-integration showcase layouts like `d6/<integration>/<feature>.json`.

### Changed

- **Docker image** — added `git` binary to production stage for sparse-checkout fixture fetching at boot.

## [1.27.1] - 2026-05-22

### Fixed

- **Router** — systemMessage array exact-match logic was unsatisfiable for 2+ needles; collapsed to substring matching. Added `elevenlabs-tts` and `translation` to endpoint compatibility filter.
- **Recorder** — Content-Type empty-string fallback (`??` → `||`), derived `EndpointType` from `FixtureMatch` instead of duplicate union, negative guards on Gemini Interactions outputs detection, scoped `turnIndex`/`hasToolResult` to chat endpoints only.
- **WS-Realtime** — session.update rollback now captures full snapshot instead of just model/type. Added Beta flat fields for noise reduction, transcription, and turn_detection. Joined all text content parts in `realtimeItemsToMessages`. Added try-catch with debug logging around `sendEvent` for WebSocket close race safety.
- **WS-Gemini-Live** — replaced deterministic `call_gemini_${name}_${i}` tool call IDs with random `generateToolCallId()` to prevent cross-turn collisions. Pre-computed `resolvedToolCalls` for wire/history ID consistency. Added unrecognized-role warning and ws.send try-catch with debug logging.
- **Gemini Interactions** — `interactionsUsage` honors Gemini-native field names (`promptTokenCount`/`candidatesTokenCount`/`totalTokenCount`). `truncateAfterChunks` only counts `content.delta` events. Added `webSearches` warning on tool-call branch.
- **fal-audio + ElevenLabs** — all journal entries now use `flattenHeaders(req.headers)` instead of `{}`. `handleSyncRun` accepts `RawJSONResponse` fixtures from queue-walk recordings.
- **Helpers** — extended `resolveUsage` with Gemini-native token fields. Preserved error cause in `resolveResponse` factory rethrow. `buildEmbeddingResponse` accepts optional usage. `extractFormField` escapes regex metacharacters.
- **Drift test infra** — retry logging with body consumption, broadened `redactUrl` to cover `api_key`/`apikey`/`token`/`access_token` patterns, URL threaded into error messages with redaction, `parseDataOnlySSE` [DONE] filter fix, `parseTypedSSE` multi-line data handling with null guards.
- **Drift collector** — invoke vitest directly via npx to avoid pnpm stdout prefix breaking JSON parse; classify raw stack traces as infrastructure errors instead of crashing.
- **AG-UI config loader** — removed `/.*/` catch-all regex fallback when `match.message` is absent; fixtures without a message pattern no longer shadow other fixtures.
- **AG-UI input validation** — runtime check that `input.messages` is an array after JSON parse; returns 400 instead of confusing downstream 404.
- **AG-UI SSE writer** — `writeAGUIEventStream` uses logger abstraction instead of `console.warn`; handles non-Error throws.
- **Drift test helpers** — `parseDataOnlySSE` handles multi-line data blocks, aligned with `providers.ts` implementation.

## [1.27.0] - 2026-05-20

### Added

- **HITL continuation recording/replay support** — `toolCallId` matching for continuation fixtures. New `toolCallId` field on `AGUIFixtureMatch` and `AGUIConfigFixture`. `getLastMessageIfToolResult` helper and `onToolResult` fluent API. Recorder uses tool-result-first priority for continuation fixtures. ([#233](https://github.com/CopilotKit/aimock/pull/233), closes [#232](https://github.com/CopilotKit/aimock/issues/232))

### Fixed

- **Walk structured content arrays in `extractLastUserMessage`** — handle multimodal user content (`AGUIMessageContentPart[]`) by joining text parts and skipping non-text. Export `NO_USER_MESSAGE_SENTINEL` constant and `AGUIMessageContentPart` type. ([#231](https://github.com/CopilotKit/aimock/pull/231))
- **Harden recorder against error responses, double-settle, and broken sentinel persistence** — guard against recording fixtures from non-2xx upstream responses, add `settled` flag to prevent error+end race, skip disk write for predicate fixtures (sentinel was semantically broken on reload), include parse error reason in SSE warning log

## [1.26.1] - 2026-05-19

### Added

- **Context-based fixture routing** — `X-AIMock-Context` header scopes fixtures per integration. Fixtures with `match.context` only match requests carrying that context; fixtures without `context` remain shared. Recorder auto-captures context and routes recorded fixtures into context subdirectories.

## [1.26.0] - 2026-05-18

### Added

- **Timing-aware recording and replay** — proxy recording captures per-frame
  arrival timestamps as `recordedTimings` on fixtures. Replay uses recorded
  timings for approximate timing reproduction based on recorded TTFT and
  inter-frame cadence instead of the synthetic model. Replay chunk count may
  differ from recording chunk count — TTFT and average pace are preserved,
  not per-token fidelity. `--replay-speed N` multiplier applies to all delay
  sources (recorded timings, streaming profiles, global latency). Per-fixture
  `replaySpeed` override. Covers SSE, NDJSON, Bedrock EventStream, and
  WebSocket protocols.

## [1.25.0] - 2026-05-18

### Added

- **Gemini `embedContent` endpoint** — `POST /v1beta/models/{model}:embedContent`
  with deterministic fallback embeddings and fixture matching
- **`/v1/images/edit` and `/v1/images/variations` endpoints** — multipart
  form-data, same response format as generations. Closes #221
- **`/v1/audio/translations` endpoint** — reuses transcription handler with
  `endpoint: "translation"` and `task: "translate"` in verbose mode
- **Ollama `/api/embeddings` endpoint** — single-embedding response, supports
  both `prompt` and `input` (string or array) fields
- **Cohere `/v2/embed` endpoint** — multi-text embedding with configurable
  `embedding_types` (float, int8, etc.)
- **ElevenLabs `/v1/text-to-speech/{voice_id}` endpoint** — binary audio
  response with voice routing and `onElevenLabsTTS` helper
- **Streaming usage chunks** — when `stream_options.include_usage` is set,
  emits a final SSE chunk with token usage before `[DONE]`
- **Automatic token usage estimation** — responses without explicit fixture
  `usage` overrides now return estimated token counts (~4 chars/token)
  instead of zeros
- **Rate limiting headers on 429 responses** — `Retry-After`,
  `x-ratelimit-limit-*`, `x-ratelimit-remaining-*`,
  `x-ratelimit-reset-*` headers on all error fixtures with status 429.
  Custom `retryAfter` override via fixture field
- **`onTranslation` convenience method** — register translation fixtures
  with endpoint discrimination
- **`onElevenLabsTTS` convenience method** — register ElevenLabs TTS
  fixtures
- **Configurable proxy timeouts** — `RecordConfig` now accepts `upstreamTimeoutMs` (default 30s) and `bodyTimeoutMs` (default 30s). The body-idle timeout is the Node socket inactivity timer that fires `req.destroy()` mid-stream; under concurrent load against reasoning models (e.g. Grok 4.3 + structured output), token-emission gaps can routinely exceed 30s during the thinking phase, causing record-mode runs to truncate SSE responses mid-stream with no `[DONE]` and no `finish_reason`. Lift to e.g. `bodyTimeoutMs: 180_000` to record cleanly under that workload.

## [1.24.1] - 2026-05-14

### Fixed

- **Gemini tool-call response serializer dropped fixture-pinned `tool_call.id`** — `parseToolCallPart` emitted `{ functionCall: { name, args } }` and omitted the id even when the fixture pinned one. Pairs with v1.23.1's INGEST-direction fix ([#196](https://github.com/CopilotKit/aimock/pull/196)) which preserves `functionCall.id` when aimock parses an _incoming_ Gemini request — that fix only helps when the id is already in the response body. Without this EGRESS-direction fix, aimock never emits one for clients to preserve in the first place, so the round-trip silently breaks for any client that depends on `functionCall.id` to correlate follow-up `functionResponse` parts back to the originating call

## [1.24.0] - 2026-05-14

### Added

- **`onFalImage(pattern, ImageResponse)`** — typed helper that wraps ImageResponse into fal's image envelope
- **`onFalVideo(pattern, VideoResponse)`** — typed helper that wraps VideoResponse into fal's video envelope
- **`MockServerOptions.falQueue`** — opt into realistic `IN_QUEUE → IN_PROGRESS → COMPLETED` polling progression with configurable thresholds
- Queue status responses include `logs[]` (state-transition entries) and `metrics.inference_time` (once COMPLETED)
- Cancel-before-completion returns `200 { status: "CANCELLED" }`; cancel-after returns `400 { status: "ALREADY_COMPLETED" }`
- Result fetch before completion returns `202` with current status body
- Queue-walk recording: recorder now walks the upstream fal queue (submit → poll → result) and persists the FINAL job body, not the submit envelope
- **`RecordConfig.fal.pollIntervalMs`** / **`fal.timeoutMs`** for tuning upstream queue-walk recording cadence
- Malformed JSON request bodies now return `400 invalid_json` (consistent with all other handlers)

### Fixed

- `pollsBeforeCompleted` auto-defaults to `pollsBeforeInProgress + 1` when only the in-progress threshold is set
- URL extension extraction no longer produces invalid MIME types for URLs with query strings, fragments, or no extension
- Double-cancel no longer pushes duplicate log entries
- Legacy fal audio queue recording now uses the same queue-walk approach

### Changed

- Default fal queue-walk timeout bumped from 2 min to 15 min (video generations routinely take 5–10 min)
- `persistFixture` and `buildFixtureMatch` extracted from recorder internals and exported for reuse

## [1.23.1] - 2026-05-14

### Fixed

- **Gemini functionCall.id preservation** — the Gemini conversation history converter generated new tool call IDs (`call_gemini_*`) instead of preserving the original IDs from `functionCall.id`. This broke `toolCallId`-based fixture matching on follow-up turns: the follow-up fixture couldn't match because the ID was overwritten, so the request fell through to `userMessage` fixtures which returned another tool call — creating an infinite loop for all Gemini/ADK showcase integrations. LangGraph-python (OpenAI format) was unaffected because it preserves IDs natively. ([#196](https://github.com/CopilotKit/aimock/pull/196))

## [1.23.0] - 2026-05-13

### Added

- **Model-aware fixture recording** — recorded fixtures now include the model name in match criteria, preventing collisions when an app makes multiple LLM calls with the same user message but different models. Model names are normalized by stripping date/version suffixes (e.g., `claude-opus-4-20250514` → `claude-opus-4`) so fixtures survive version bumps. Disable with `recordFullModelVersion: true`. ([#185](https://github.com/CopilotKit/aimock/issues/185))
- **Drift detection metadata** — recorded fixtures include `systemHash` and `toolsHash` in a `metadata` block for detecting system prompt or tool definition changes since recording.
- **Prefix model matching** — fixture router uses `startsWith` for string model matching, so `model: "claude-opus-4"` matches any `claude-opus-4-*` version.
- **GA Realtime protocol migration with Beta compatibility shim** — handler emits GA event names natively; `sendEvent()` wrapper translates back for Beta clients detected via `OpenAI-Beta` header. Default model changed to `gpt-realtime-2`.
- **GA Realtime models** — `gpt-realtime`, `gpt-realtime-2`, `gpt-realtime-1.5`, `gpt-realtime-mini` (and dated snapshots). Transcription/translation sessions use `gpt-4o-transcribe`, `gpt-4o-mini-transcribe`, or `whisper-1`.
- **Transcription and translation session types** — dedicated session configurations for translation and transcription workloads on the Realtime API.
- **Image input support** — Realtime sessions accept image content parts alongside text and audio.
- **Commentary phase** — Realtime handler supports the GA commentary phase for model-generated annotations.
- **`conversation.item.done` and `response.cancel` events** — new GA Realtime event types for item completion tracking and response cancellation.
- **Endpoint type routing for Realtime** — router distinguishes GA vs Beta Realtime endpoints for fixture matching.
- **Drift detection for GA Realtime** — drift test suite extended with GA protocol shapes, Beta conformance shapes, and three-way triangulation.

### Tests

- **73 GA Realtime integration tests** — comprehensive test coverage for all GA event types, Beta compatibility, session management, model routing, image input, translate/whisper, commentary, and cancellation.
- **GA and Beta Realtime conformance suites** — API conformance tests validating event shapes against both GA and Beta protocol specs.
- **GA Realtime drift detection** — SDK shape tests and provider triangulation for the GA Realtime protocol.

## [1.22.1] - 2026-05-12

### Fixed

- **Strict mode checked before proxy attempt** — in `--proxy-only` mode, the `X-AIMock-Strict` header had no effect because `proxyAndRecord()` returned before the strict check. Now all 17 handlers check strict mode first: when strict + no fixture → 503 immediately, no proxy attempt
- **Helper utilities and error serialization** — hardened helper functions and error serialization paths for correctness and robustness
- **Journal and fixture-loader correctness** — fixed journal entry handling and fixture-loader edge cases
- **WebSocket handler consistency and strict-mode journal** — aligned WebSocket handler behavior and ensured strict-mode journal entries are recorded correctly
- **Provider handler consistency and proxy outcomes** — unified provider handler error paths and proxy outcome reporting
- **Media handler hardening and chaos injection** — strengthened media handler validation and chaos injection reliability

### Tests

- **Bedrock mock consistency and CLI help text** — corrected Bedrock mock test assertions and CLI `--help` output coverage

## [1.22.0] - 2026-05-11

### Added

- **Per-request strict mode via `X-AIMock-Strict` header** — overrides the server-wide `--strict` flag per request (`true`/`1` = strict, `false`/`0` = lenient). When strict: fixture miss returns 503; when lenient: fixture miss proxies to real provider. Follows the `X-AIMock-Chaos-*` precedence pattern. Journal entries record `strictOverride` when the header overrides the server default. Enables the same aimock instance to serve both deterministic test probes and live demo traffic simultaneously.

### Fixed

- **Progressive relay for NDJSON and Bedrock binary event streams** — Ollama NDJSON and Bedrock binary event streams were fully buffered before relay, triggering downstream idle timeouts; now relayed progressively as chunks arrive
- **JSON.parse error detail in bare catch blocks** — capture and surface parse-error detail in all bare catch blocks across 25+ provider/WebSocket/stream-collapse handlers instead of swallowing context
- **Unguarded stream write/end calls** — wrap stream write/end in try/catch (recorder.ts, agui-recorder.ts) to prevent unhandled exceptions on client disconnect
- **Response termination for headers-already-sent paths** — add response termination in error paths where headers were already sent (server.ts, a2a-mock.ts, mcp-mock.ts), preventing connection hangs
- **Vector-mock double body consumption** — fix route passthrough consuming the request body twice, causing empty-body forwarding
- **Drift detection compared only first event per type** — `compareSSESequences` now compares ALL events per type, not just the first, catching previously invisible divergences
- **Ollama drift tests used broken async describe.skipIf** — replaced with synchronous env-var gate so tests are correctly skipped or executed
- **12 unrestored spy/mock leaks and misleading assertions** — fix spy/mock leaks across test files and correct assertions that passed for the wrong reasons
- Proxy relay hardcoded POST method — now forwards the original HTTP method
- Response timeout timer leak — cleared after successful upstream completion
- Client disconnect handler race — checks `writableFinished` before destroying upstream request
- `onHookBypassed` and `beforeWriteResponse` callbacks not wrapped in try/catch
- Audio error relay sent non-2xx responses with audio content-type instead of application/json
- Snapshot-mode fixture writes not atomic — concurrent requests could corrupt the file
- Undefined `toolCall` name/arguments silently dropped during fixture save
- Video detection heuristic false-positives on LLM provider responses with `{id, status}` shape
- One-shot error fixture splice during iteration (deferred via microtask)
- Azure model injection catch swallowed non-SyntaxError exceptions
- fal request body lost on passthrough (double `readBody` consumption)
- fal queue handler dropped PUT request body
- Recorder test: tmpDir leak on strict-mode reassignment, global fetch dependency, fragile fixturePath cleanup, duplicate helpers, spy leak on assertion failure

### Added

- Fixture-level chaos evaluation for non-completions endpoints (ElevenLabs, fal)

### Changed

- **Anti-buffering headers on all progressive stream relay paths** — standard headers (Cache-Control, Connection, X-Accel-Buffering) added to all progressive stream relay paths to prevent intermediate proxy buffering
- **Stream-collapse returns firstDroppedSample** — stream-collapse functions now return the first dropped sample for forensic debugging of collapsed streams

## [1.21.0] - 2026-05-11

### Added

- **`match.systemMessage` accepts `string[]`** — array form requires ALL substrings to be present in the joined system-message text (AND semantics). Use this when the gate must combine multiple non-adjacent tokens that may appear in any order — e.g., a host that serialises agent-context entries into a system message whose entry order is not stable, but where a fixture should only match when every default value is present (`["\"value\": \"Atai\"", "[\"Viewed the pricing page\",\"Watched the product demo video\"]"]`). Single-string and `RegExp` forms continue to work unchanged. JSON form accepts `string | string[]`; programmatic form accepts `string | string[] | RegExp`.

## [1.20.0] - 2026-05-11

### Fixed

- **Drift tests passed vacuously with zero assertions** — the `shouldFail` guard silently skipped all `expect` calls when no critical diffs were found, so broken extraction logic or warning-level drift went completely undetected. Replaced every guarded assertion across all 21 drift test files (89 instances) with unconditional `expect(diffs.filter(...)).toEqual([])`
- **Proxy relay leaked raw upstream HTTP status codes** — 5 recorder relay paths in `recorder.ts` and `agui-recorder.ts` forwarded raw upstream codes (429, 503, 401, 201, etc.) to aimock clients, exposing provider implementation details. Normalized to 200 for success and 502 for errors; fixture recording preserves the original status for fidelity

### Added

- **`match.systemMessage` fixture matcher** — gate a fixture on a substring (or regexp) found inside the concatenated text of every `system` role message in the request. Hosts that plumb dynamic context (persona, agent-context entries, dynamic config) through system messages can now narrow a fixture to a specific context state; when the caller changes that state the fixture stops matching and the request falls through to the next fixture or upstream proxy instead of silently returning a stale baked response. JSON form: `"match": { "userMessage": "Who am I?", "systemMessage": "name=Atai" }`. Programmatic form accepts `string | RegExp`.
- **Status code normalization tests** — 5 tests verifying proxy relay normalization (201→200, 429→502, 503→502, 401→502, SSE 429→502) with fixture preservation assertions; 2 existing tests updated to expect normalized 502

## [1.19.5] - 2026-05-09

### Fixed

- **Responses API request conversion** — forward `max_output_tokens` and `response_format` from Responses requests to the underlying Chat Completions call
- **Gemini request conversion** — forward `maxOutputTokens`, `topP`, `topK` from `generationConfig`; remove synthetic `functionCall.id` that real Gemini does not produce
- **Cohere request conversion** — structured content (images, documents), native tool definitions, `temperature`, `max_tokens`, and `stop_sequences` now forwarded
- **Ollama request conversion** — `tool_calls` on assistant messages, base64 `images` on user messages, `system` parameter on `/api/generate`
- **Chat Completions error responses** — add `param` field per OpenAI error spec
- **Moderation response shape** — correct `categories` and `category_scores` to match the real OpenAI moderation object (boolean flags + float scores)
- **Transcription verbose response** — add `task`, `duration`, `segments`, `words` fields for `verbose_json` format
- **Search response shape** — add `status` field to search results
- **Rerank response shape** — wrap results in `{ results: [...] }` with `relevance_score` per result
- **Realtime WebSocket** — add `previous_item_id` to conversation items, correct event ID prefixes, add missing fields on session and response events
- **Gemini Live WebSocket** — `generationConfig` alias for `generation_config`, `turnComplete` server event, correct gRPC status codes in error events, complete `httpToGrpc` mapper
- **Anthropic thinking blocks** — add `signature` field to `thinking` content blocks and `signature_delta` event type for extended thinking with signatures

### Added

- **Drift tests for 9 multimedia/auxiliary providers** — images, speech/TTS, transcription/STT, moderation, ElevenLabs audio, fal.ai, fal.ai queue lifecycle, video, rerank
- **Error shape drift tests** — OpenAI Chat, Anthropic Claude, Gemini, Cohere error response shapes validated against SDK types
- **Reasoning/thinking drift tests** — OpenAI Chat `reasoning_effort`, OpenAI Responses `reasoning`, Anthropic `thinking` content blocks, Gemini `thinking_config`

## [1.19.4] - 2026-05-08

### Fixed

- **Converse stream: spurious `type` field in contentBlockDelta and contentBlockStart** — `delta` objects contained a Claude Messages API `type` field (`text_delta`, `thinking_delta`) that is not a member of the Converse API's tagged union. botocore's single-member union parser rejected the extra field with `ResponseParserError`. Also fixed reasoning deltas to use `reasoningContent` (Converse format) instead of `thinking` (Claude format). (Issue #165, reported by @KMiya84377)
- **Converse: `inferenceConfig.maxTokens` silently dropped** — `converseToCompletionRequest` now forwards `maxTokens` to `max_tokens`
- **Converse: non-streaming responses missing `metrics`** — Added `metrics: { latencyMs: 0 }` to all 3 non-streaming converse response builders, matching the streaming path and AWS ConverseResponse spec

### Added

- **Bedrock drift test expansion** — invoke-with-response-stream drift test with binary frame parsing and Anthropic-native event shape comparison. Converse-stream SDK shapes for tool call (`toolUse` start/delta) and reasoning (`reasoningContent` start/delta) variants. Three-way triangulate comparisons for all variants.

## [1.19.3] - 2026-05-08

### Fixed

- **Recorder: multi-turn runs collapsed to a single fixture**. `--record` mode wrote only `userMessage` to the saved fixture's `match` block, so two LLM calls in the same agent run that share the user message (the canonical tool-call → tool-result → follow-up shape) collided in the in-memory fixture cache: as soon as turn 0 was recorded, turn 1 matched it on the same `userMessage` substring, the recorder was skipped, and the follow-up text turn was silently lost. Replaying the resulting single fixture against the same run looped on the tool call until the framework's recursion limit fired. Recorder now also writes `turnIndex` (count of `assistant` messages) and `hasToolResult` (any `role:"tool"` present) — the matcher in 1.16.0 already accepts both — so each call in a run produces a distinct, deterministic match key. Existing single-`userMessage` fixtures continue to match unchanged. Surfaced as: CopilotKit's beautiful-chat showcase having to hot-patch `dist/recorder.js` inside its running aimock container to make `--record` produce usable multi-turn recordings.

## [1.19.2] - 2026-05-07

### Fixed

- **Converse stream: double-wrapped Event Stream payloads** — `buildBedrockStreamTextEvents`, `buildBedrockStreamToolCallEvents`, and `buildBedrockStreamContentWithToolCallsEvents` emitted payloads wrapped with the event type name (e.g. `{ messageStart: { role: "assistant" } }`). The `:event-type` header already carries the event name, so AWS SDK (botocore) expected flat payloads (e.g. `{ role: "assistant" }`). The redundant wrapping caused botocore's `BaseEventStreamParser` to silently return empty dicts, producing `KeyError: 'role'` in downstream frameworks like Strands Agents. (Issue #162, reported by @KMiya84377)
- **Responses API: missing item_id on 3 SSE event types** — Added `item_id` to `response.output_text.done`, `response.content_part.added`, and `response.content_part.done` events, matching the real OpenAI Responses API shape. SDK drift shapes updated.
- **Chat Completions: missing logprobs on choices** — Added `logprobs: null` to all streaming chunks and non-streaming choices. Removed `logprobs` from drift allowlist so future omissions are caught.
- **Ollama: missing created_at on /api/chat** — Added `created_at` to all 6 `/api/chat` builder functions (text, tool call, content+tools, and their streaming variants). The `/api/generate` path already had it.
- **Gemini: error fixtures used Anthropic-style error codes** — Test fixtures and the Gemini Live WebSocket handler now use Google canonical gRPC status codes (`RESOURCE_EXHAUSTED`, `INTERNAL`) instead of `rate_limit_error` / `ERROR`.

## [1.19.1] - 2026-05-07

### Fixed

- **Fixture loader: snapshot-style subdirectories skipped on boot** — `loadFixturesFromDir` now recurses one level into subdirectories to load `<testId>/<provider>.json` files written by the snapshot recorder. Previously the loader skipped all subdirectories with a warning, so recorded fixtures could never be replayed. (Issue #161, reported by @jantimon)
- **CLI: immediate exit on npx/bunx** — Entry-point guard now matches the `aimock` bin name (not just `aimock-cli.js`), fixing silent exit when invoked via `npx aimock` or `bunx aimock`. (Issue #160)

## [1.19.0] - 2026-05-06

### Added

- **Async fixture responses** — Fixture responses can now be sync or async functions that receive the request and return the response dynamically. Enables awaiting side effects (database writes, API calls) before constructing the response — eliminating race conditions in complex multi-turn E2E tests. Works with all providers, streaming, and convenience methods (`on()`, `onMessage()`, `onTurn()`, `onToolCall()`, `onToolResult()`). (Feature request by @5ebastianMeier, issue #154)
- **Snapshot-style recording** — When `X-Test-Id` is present, recorded fixtures are saved to `<fixturePath>/<slugified-testId>/<provider>.json` instead of timestamp-based filenames. Multiple fixtures for the same test+provider merge into one file. Stable paths enable meaningful PR diffs and easy test-to-fixture mapping. (Feature request by @jantimon, issue #155)

### Fixed

- **CORS: Firefox preflight blocked by restricted `Allow-Headers`** — Changed `Access-Control-Allow-Headers` from `Content-Type, Authorization` to wildcard `*`, fixing Firefox's strict CORS enforcement when the OpenAI SDK sends `User-Agent` in the preflight. (Issue #158)
- **GitHub Action: cosmetic binary rename** — `action.yml` fixtures branch referenced the legacy `llmock` binary (still functional); updated to `aimock` for consistency
- **GitHub Action: hardcoded URLs in docs examples** — All workflow examples now use `steps.<id>.outputs.url` instead of hardcoded `http://127.0.0.1:4010`

## [1.18.0] - 2026-05-04

### Added

- **Chaos testing in proxy mode** — Pre-flight chaos (drop/disconnect) prevents upstream contact; post-response chaos (malformed) corrupts relay body after recording the real upstream response. SSE bypass tracked via `aimock_chaos_bypassed_total` metric. Explicit `source` label (`fixture`/`proxy`/`internal`) on all chaos Prometheus counters and journal entries.
- **fal.ai as first-class provider** — General fal.ai handler (`src/fal.ts`) supporting arbitrary JSON request/response payloads (image, video, motion, music, etc.) alongside the existing audio-only `fal-audio.ts`. Routes by `x-fal-target-host` header. Queue lifecycle (submit→status→result→cancel), sync run, storage upload stub. `RawJSONResponse` type for verbatim JSON fixture preservation. Convenience methods `onFalQueue()` and `onFalRun()`. Record and replay support. (PR #153, tombeckenham)

### Fixed

- **Fixture validation for RawJSONResponse** — `validateFixtures()` now recognizes `RawJSONResponse`, fixing the record→restart→replay cycle for fal.ai fixtures loaded from disk

## [1.17.0] - 2026-05-04

### Added

- **Gemini Interactions API** — 12th LLM provider. Full record/replay support for Google's Gemini Interactions streaming API (`/v1beta/models/{model}:streamGenerateContent`), including multi-turn conversations, function calling, streaming text, and safety metadata. Drift tests, integration tests, and documentation included. (PR #139)
- **AG-UI interrupt-aware types** — `AGUIInterrupt`, `AGUIResumeEntry`, and `AGUIRunFinishedOutcome` (discriminated union: success | interrupt) from ag-ui PR #1569. `outcome` field on `AGUIRunFinishedEvent`, `resume` field on `AGUIRunAgentInput`.
- **AG-UI convenience builders** — `buildActivityDelta`, `buildToolCallChunk`, `buildRawEvent`, `buildCustomEvent`, `buildReasoningChunk`, `buildReasoningEncryptedValue`.
- **`./agui` subpath export** — `agui-stub.ts` wired into `tsdown.config.ts` and `package.json` exports, matching `./a2a`, `./mcp`, `./vector` pattern.
- **Complete AG-UI type exports** — All event types (reasoning, step, thinking, raw, custom, chunk), `AGUIBuildOpts`, `matchesAGUIFixture`, `AGUIReasoningEncryptedValueSubtype`, `AGUIMessageRole` now exported from package root.
- **`"warn"` log level** — New log level between `"silent"` and `"info"` with proper hierarchy. `AGUIMockOptions.logLevel` option added; AG-UI mock defaults to `"warn"` instead of `"silent"`.
- **Non-speech audio generation** — Mock support for ElevenLabs sound effects (`/v1/sound-generation`) and music (`/v1/music/*`), fal.ai queue-based audio (`/fal/queue/submit/*`, `/fal/queue/requests/*`, `/fal/run/*`), Gemini HTTP audio via `generateContent`/`streamGenerateContent` with `inlineData` audio parts, and Gemini Live WebSocket audio. Convenience methods: `onAudio()`, `onSoundEffect()`, `onMusic()`, `onFalAudio()`. (PR #140, closes #118)
- **AudioResponse broadened** — `audio` field now supports both `string` (base64) and `{ b64Json, contentType }` object form
- **Gemini audio recording** — Record and replay Gemini audio responses (both streaming SSE and non-streaming JSON)
- **Router audio-gen filtering** — Bidirectional endpoint filtering for `audio-gen` and `fal-audio` endpoint types

### Fixed

- **AG-UI type alignment** — `AGUIMessage.id` and `AGUIRunAgentInput.threadId`/`runId` now required (was optional). `AGUIToolDefinition.description` now required, `metadata` field added. `encryptedValue` and `error` fields added to `AGUIMessage`. `AGUIMessageRole` union covers all 7 protocol roles.
- **AG-UI handler hardening** — `writeAGUIEventStream` logs all caught errors (was silently swallowing non-TypeError/RangeError), preserves user-supplied timestamps (was always overwriting with `Date.now()`). `buildCompositeResponse` omits `RUN_FINISHED` when inner events contain `RUN_ERROR` (protocol compliance). `matchesFixture` resets `lastIndex` on regex before `test()`.
- **AG-UI recorder SSE parsing** — Handle `data:` lines without space after colon per SSE spec. Serialize predicate-match fixtures as `__NO_USER_MESSAGE__` sentinel on disk instead of producing catch-all `{}`. Return actual HTTP status from proxy (was always boolean/200). Forward upstream content-type on non-2xx responses instead of SSE headers.
- **AG-UI mock error handling** — `readBody` wrapped in try/catch (was unguarded await producing opaque 500s). JSON parse errors now include detail in 400 response. Proxy status correctly journaled.
- **Drift auto-remediation pipeline** — `drift-report-collector` now clones canonical ag-ui repo and runs AG-UI schema drift test alongside HTTP API drift. `test-drift.yml` Slack notification distinguishes AG-UI schema vs HTTP API drift vs infra error; uses `jq` for safe JSON construction. `fix-drift.yml` clones ag-ui before collection, adds `continue-on-error` on autofix with `success()` guard on PR creation. `fix-drift.ts` corrected `src/agui.ts` references to `src/agui-handler.ts`, added SDK type to drift prompt, flexible changelog title matching, ENOENT-specific catch in `syncDescriptionFromReadme`, exit code 4 for no-changes (was collision with exit 2), version bump wrapped in try/catch.
- **AG-UI schema drift test hardening** — `skipIf` guard checks both source files (was only canonical). Field regex handles multi-arg Zod types. Recursive parent field resolution for multi-level inheritance. Comment lines stripped before field matching. Base fields parsed dynamically from source with hardcoded fallback.

## [1.16.4] - 2026-04-30

### Fixed

- **Router: `toolCallId` matched stale tool messages from history**. The matcher previously used `getLastMessageByRole(messages, "tool")`, so once a conversation contained any prior tool result, every subsequent request still had a "last tool message" buried in history — a `toolCallId` fixture could win and shadow `userMessage` matchers for new user turns. Tightened to require the tool message to be the **last** message in the request (which is the only state in which the LLM is being asked to respond to a tool result). Surfaced as: in CopilotKit's beautiful-chat showcase, clicking a second suggestion replayed the first chart's content fixture instead of producing a new tool call.

## [1.16.3] - 2026-04-29

### Fixed

- **Responses API**: `response.function_call_arguments.done` events now include `item_id` field, matching the real OpenAI Responses API shape. Without this, TanStack AI's OpenAI adapter emits `TOOL_CALL_END` with `toolCallId: undefined`, breaking ag-ui verify middleware.

## [1.16.2] - 2026-04-28

### Fixed

- **Bedrock invoke native stream format** — `invoke-with-response-stream` now emits Anthropic-native snake_case payloads (`content_block_delta`, `input_json_delta`) wrapped in Bedrock EventStream `chunk` frames, instead of Converse-style camelCase events. Converse-stream retains camelCase format. (PR #144, sf-jin-ku)
- **Bedrock invoke false-green test** — Reasoning negative test used wrong event filters, masking a real bug; corrected to match actual stream shape (PR #144)
- **Bedrock invoke/stream hardening** — Set `completionReq.stream = true` in streaming handler; use deterministic `tool_use_${index}` fallback IDs; change `textContent || null` to `?? null` to preserve empty strings; warn on unsupported content block types and unexpected roles; add webSearches warning on tool-call-only responses
- **Converse stream shape alignment** — Wrap `contentBlockStop` and `messageStop` payloads to match real AWS Converse API; remove duplicate top-level `contentBlockIndex` from `contentBlockStart`/`contentBlockDelta`; add trailing `metadata` events (usage + latencyMs) to all stream builders
- **Converse request conversion** — Filter empty-string text blocks in all paths; unwrap `inputSchema` from `{ json: {...} }` Converse API wrapper; set `completionReq.stream = true` in streaming handler; add content-loss warnings for non-text blocks; fix error type `||` to `??`

### Changed

- Extract shared test helpers (`createMockReq`/`createMockRes`/`createDefaults`) into `helpers/mock-res.ts`
- Convert reasoning-all-providers tests to per-test server lifecycle
- Add content+toolCalls streaming integration coverage for both invoke and converse paths (PR #144)

## [1.16.1] - 2026-04-28

### Fixed

- **Responses API: item_reference dropped** — `responsesInputToMessages()` now synthesizes an assistant message with a matching `function_call` when a `function_call_output` has no prior matching call, preventing item_reference loss
- **Responses API: annotations missing** — Added `annotations: []` to all four `output_text` content items (streaming `.added`, `.done`, prefix, and non-streaming) for schema conformance
- **Responses API: item_id missing on reasoning events** — Added `item_id` to `reasoning_summary_part.added`, `reasoning_summary_part.done`, and `reasoning_summary_text.done` events
- **Responses API: web_search_call action missing type** — Changed `action: { query }` to `action: { type: "search", query }` in both streaming events and output prefix
- **Responses API: item_reference for text messages** — Extended item_reference handling to cover assistant text messages, not just function_call_output compensation
- **Responses API: multi-fco assistantCount inflation** — Fixed backward scan in `responsesInputToMessages()` to find and append to existing assistant messages with tool_calls instead of creating duplicates

### Added

- **Debug logging across all LLM handlers** — Added `logger.debug("Fixture matched: ...")` on match and `logger.debug("No fixture matched...")` on no-match to: server.ts, responses.ts, messages.ts, gemini.ts, bedrock.ts, bedrock-converse.ts, cohere.ts, ollama.ts, embeddings.ts, images.ts, speech.ts, transcription.ts, video.ts

## 1.16.0

### Added

- **`turnIndex` match criterion**: Stateless conversation-depth matching — counts `role: "assistant"` messages in the request's message array. Use for multi-turn conversation flows in shared/deployed instances where `sequenceIndex` counters break under concurrency. `turnIndex: 0` matches the first turn (no prior assistant messages), `turnIndex: 1` the second, etc.
- **`hasToolResult` match criterion**: Stateless boolean — `true` when any `role: "tool"` message exists in the request, `false` when none do. Simplest option for 2-step HITL flows (tool call → tool result → follow-up).
- `onTurn(turn, pattern, response, opts)` convenience method on the programmatic API.

## 1.15.1

### Fixed

- **Recorder**: crash hardening (headersSent guards, clientDisconnected tracking),
  preserve content alongside toolCalls, Cohere v2 native detection, tool-call ID
  extraction from 5 providers, reasoning/thinking extraction from 4 providers,
  multi-block text join (filter+join instead of find), thinking-only and empty-content
  response handling, Ollama /api/generate format detection, streaming collapse
  reasoning propagation.
- **Bedrock/Converse**: ContentWithToolCallsResponse support, ResponseOverrides wired
  into all non-streaming and streaming builders, Converse-wrapped stream event format,
  text_delta type field on text deltas, proper error envelope on Converse errors,
  webSearches warnings.
- **Cohere v2**: reasoning in all builders + streaming, webSearches warnings,
  response_format forwarding, assistant tool_calls preservation, full
  ResponseOverrides (finish_reason, usage, id) in non-streaming and streaming paths.
- **Server**: readBody 10MB size limit, control API error detail, one-shot error fixture
  race fix, normalizeCompatPath clarity, fixtures_loaded gauge updates on mutations.
- **Competitive matrix**: HTML pipeline fixed (computeChanges, applyChanges,
  updateProviderCounts, extractFeatures all aligned with actual DOM structure).
- **CI workflows**: --auto merge (respects branch protection), Slack secrets via env
  vars, script injection prevention in notify-pr.yml, portable grep.
- **Router**: RegExp g-flag lastIndex reset prevents alternating match/no-match.
- **Jest/Vitest**: save/restore pre-existing env vars in afterAll, loadFixtures
  console.warn on failure.
- **Gemini**: tool_call_id collision fix (shared callCounter), thought-part filtering.
- **Ollama**: ContentWithToolCallsResponse support, default stream:true, field validation.

## 1.14.9

### Fixed

- Removed the `preinstall: npx only-allow pnpm` script from the published package. That
  hook was intended to guide contributors cloning the monorepo toward pnpm, but it got
  bundled into the published tarball and fired during `npm install -g @copilotkit/aimock`,
  aborting the install with `sh: only-allow: not found` before npm could even resolve the
  binary. The guard belongs on the monorepo root, not inside the shipped package.
  Unblocks CopilotKit's docs CI (and any other consumer installing aimock via npm).

## 1.14.8

### Fixed

- `--proxy-only` mode now accepts URL-only `--fixtures` sources without requiring a local
  filesystem path. Previously the first `--fixtures` value was always checked as a
  record-destination base path, which rejected all-URL invocations even though proxy-only
  mode doesn't write recordings to disk. The check now fires only for `--record` mode
  where a writable destination is actually required. Same fix applied to the parallel
  `--agui-proxy-only` CLI path. Unblocks the showcase-aimock Railway service which runs
  aimock in proxy-only mode with remote GitHub raw fixture URLs and no local fallback.

## 1.14.7

### Added

- `--fixtures` now accepts `https://` and `http://` URLs to JSON fixture files in addition to filesystem paths. Fetches at boot, parses, and registers the remote fixture as if loaded from disk. On-disk cache at `~/.cache/aimock/fixtures/<sha256-of-url>/` (honoring `$XDG_CACHE_HOME`) provides resilience against transient upstream failures: with `--validate-on-load`, a fetch failure with a valid cached copy logs a warning and continues; without a cache, the process exits non-zero. HTTP fetch has a hard-coded 10s timeout and a 50 MB body size cap (enforced incrementally so a lying `Content-Length` cannot bypass it). Only `https://` and `http://` schemes are accepted — `file://`, `ftp://`, etc. are rejected with a clear error. The flag is now repeatable; multiple sources are loaded and concatenated. Tarball (`.tar.gz`) and zip URL support intentionally deferred to a future release.
- Private-address denylist for remote `--fixtures` URLs: fetches to loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), RFC1918 (`10/8`, `172.16/12`, `192.168/16`), CGNAT (`100.64/10`), cloud-metadata (`169.254.169.254`), ULA (`fc00::/7`), multicast, and other reserved ranges are rejected with a clear fail-loud error. Hostnames are resolved and every returned address is checked. Set `AIMOCK_ALLOW_PRIVATE_URLS=1` to opt out (required for local dev / tests that target `127.0.0.1`).
- HTTP redirects are rejected (fail-loud) for remote `--fixtures` URLs to prevent scheme-bypass (a 3xx `Location:` pointing at `file://` or `javascript:` would otherwise sidestep the scheme gate and SSRF denylist). Configure the upstream to serve the final URL directly — GitHub raw content URLs already do this.

## 1.14.6

### Changed

- README DevX: Quick Start sets `OPENAI_BASE_URL` + `OPENAI_API_KEY` before SDK construction with an inline ordering warning; Docker one-liner uses absolute `$(pwd)/fixtures:/fixtures` path; `LLMock` class name asymmetry after the v1.7.0 package rename is explained inline; Multimedia and Protocol-Mock feature bullets now link to each individual feature page.
- Fixtures page: Vertex AI added to Provider Support Matrix; Ollama Reasoning marked as supported (was incorrectly "—" since v1.8.0); `finishReason` Responses-API mapping fully documented; `toolName` scope clarified; shadowing-warning format matches actual validator output; Azure-inherits-OpenAI override support footnoted.
- Record & Replay page: Docker examples use absolute `$(pwd)` paths; Rust `async-openai` example corrected to `Client::with_config(OpenAIConfig::new().with_api_base(...))` form; `enableRecording({ proxyOnly: true })` disambiguated; pseudocode annotated as simplified; `enableRecording` example includes `mock.stop()` cleanup; stale 2025 timestamp replaced with generic placeholder.
- Sidebar: TOC id-assignment now runs unconditionally (previously skipped on pages with fewer than 4 headings, silently breaking cross-page anchor links to short pages).
- Historical CHANGELOG: v1.14.1 Railway-specific language scrubbed; v1.14.2 `--journal-max=-1` rejection and `createServer()` default flip annotated with BREAKING / BEHAVIOR CHANGE markers; all 15 historical version entries standardized on Keep-a-Changelog categories (Added/Changed/Fixed/Removed) instead of mixed Changesets-style.
- package.json: `engines.node` raised to `>=24.0.0` to match OIDC publish requirement; `preinstall: only-allow pnpm` guard added; deprecated `@google/generative-ai` swapped for `@google/genai`; `files` includes `CHANGELOG.md`; `repository.url` canonicalized; `typesVersions` gains `.d.cts` entries; optional `peerDependencies` for `vitest`/`jest` added; `prepare: husky || true` tightened to `husky`; `release` script gains `pnpm test && pnpm lint` pre-check.

### Removed

- Stray `package-lock.json` — repo is pnpm-only, now enforced via `preinstall`.

## 1.14.5

### Fixed

- Recorder no longer buffers SSE (`text/event-stream`) upstream responses before relaying to the client. `proxyAndRecord` accumulated all upstream chunks and replayed them via a single `res.end()`, collapsing multi-frame streams into one client-visible write and breaking progressive rendering for downstream consumers (notably showcase `--proxy-only` deployments). SSE responses now stream chunk-by-chunk to the client while still being tee'd into the recording buffer; non-SSE behavior is unchanged.

## 1.14.4

### Added

- Multi-turn conversations documentation page covering the tool-round idiom, matching semantics across turns, and how to author/record multi-turn fixtures.
- Matching Semantics section on the Fixtures page documenting last-message-only matching, first-wins file order, substring-vs-exact matching, and shadowing warnings.
- Recording guidance for multi-turn conversations on the Record & Replay page.
- CLI Flags table on the Record & Replay page expanded to cover `-f/--fixtures`, `--journal-max`, `--fixture-counts-max`, `--agui-*`, `--chaos-*`, `--watch`, `-p`, `-h`, `--log-level`, `--validate-on-load`.
- README note clarifying that the `llmock` CLI bin is a legacy alias pointing at a narrower flag-driven CLI without `--config` or `convert` support.

### Fixed

- Docker examples in the Record & Replay guide no longer prefix `npx @copilotkit/aimock` before the image ENTRYPOINT (the four snippets would have failed with strict parseArgs rejecting positional args).
- Auth Header Forwarding documentation now reflects the strip-list behavior that has been in place since v1.6.1 (all headers forwarded except hop-by-hop and client-set).
- `requestTransform` example fixture key no longer carries an undocumented load-bearing trailing space.
- Completed the Claude model-id migration (v1.14.3) for the remaining test fixtures that still referenced `claude-sonnet-4-20250514`.
- README LLM Providers count and migration-page comparisons restored to the "11+" form with accurate enumeration (OpenAI Chat / Responses / Realtime, Claude, Gemini REST / Live WS, Azure, Bedrock, Vertex AI, Ollama, Cohere). The earlier "8" collapse was incorrect: competitors count endpoint/protocol variants separately, and "8" undersold aimock's actual coverage. Provider Support Matrix on the Fixtures page gains a dedicated Vertex AI column.
- Corrected `toolCallId` matching semantics on the Fixtures page to describe the "last `role: "tool"` message" rule from `router.ts` (not "last message being a tool").
- Added `-h 0.0.0.0` to every Docker example in the README and Record & Replay page so the default `127.0.0.1` host bind doesn't silently break `-p` port mapping when user args override the image CMD.
- Extended the Docker host-bind fix across all migration guides, tutorials, and the Docker/aimock-cli/metrics/chaos-testing pages — every Docker example that passes user args now includes `-h 0.0.0.0` so `docker -p` port mapping works.
- Updated `--journal-max` default wording on the Record & Replay page to reflect post-v1.14.2 behavior (finite `1000` cap for both `serve` and `createServer()`; only direct `new Journal()` instantiation remains unbounded).
- Stripped redundant `npx @copilotkit/aimock` / `aimock` prefixes from Docker examples in migration pages (mokksy, vidaimock, mock-llm, piyook, openai-responses); all were silently broken under strict parseArgs because the prefix became a positional arg to the image's `node dist/cli.js` entrypoint.
- Replaced `--config` Docker examples across `docs/aimock-cli`, `docs/metrics`, `docs/chaos-testing`, and migration guides with flag-driven Docker equivalents or explicit npx/local-install notes (the published image's ENTRYPOINT runs the `llmock` CLI which does not support `--config`).
- Synchronized LLM provider counts across all migration pages to the "11+" form with accurate variant-level enumeration, restoring competitor-equivalent counting (e.g. VidaiMock "11+", Mokksy "11 vs 5").
- Corrected the `sequenceIndex` gotcha on `/multi-turn` — `validateFixtures` does not factor `sequenceIndex`, `toolCallId`, `model`, or `predicate` into the duplicate-`userMessage` warning; the warning is advisory when a runtime differentiator is present.
- Fixed the Programmatic Recording example on `/record-replay` to stop contradicting itself by pairing `proxyOnly: true` with `fixturePath`; now shows record mode and proxy-only mode as two distinct examples.
- Reconciled provider-count phrasing across migration pages — mock-llm lead paragraph no longer says "9 more providers", enumerated lists no longer trail the count with "and OpenAI-compatible providers" / "and more". Aligned the `validateFixtures` shadowing wording between the Fixtures and Multi-Turn pages (both now correctly describe the warning as advisory when a runtime differentiator is present).
- Replaced broken `class="cmt"` CSS class with correct `class="cm"` across `docs/cohere`, `docs/test-plugins`, `docs/vertex-ai`, `docs/ollama`, `docs/record-replay`, and `docs/chaos-testing` code blocks (21 occurrences) — `.cmt` is not defined in `docs/style.css`, so these code-block comments were rendering as default text instead of the dimmed comment color.

## 1.14.3

### Added

- Microsoft Agent Framework (MAF) integration guide with Python and .NET examples.
- Generic `.code-tabs` language switcher with cross-section sync and localStorage persistence.

### Changed

- Updated Claude model references from `claude-sonnet-4-20250514` (retiring 2026-06-15) to `claude-sonnet-4-6`.

## 1.14.2

> **BREAKING** — CLI flag parsing: `--journal-max=-1` (and `--fixture-counts-max=-1`) no longer silently maps to "unbounded"; it is now rejected with a clear error. Migration: drop the flag entirely, or pass `--journal-max=0` / `--fixture-counts-max=0` if you intended unbounded retention.
>
> **⚠ BEHAVIOR CHANGE (should have been MINOR per SemVer)** — `createServer()` programmatic defaults for `journalMaxEntries` and `fixtureCountsMaxTestIds` flipped from unbounded to finite caps (1000 / 500). Auto-update consumers on long-running embedders: review your retention assumptions and opt in to unbounded explicitly by passing `0` if that was the prior relied-upon behavior. Released as a PATCH; in retrospect this warranted a MINOR bump.

### Fixed

- `Journal.getFixtureMatchCount()` is now read-only: calling it with an unknown testId no longer inserts an empty map or triggers FIFO eviction of a live testId. Reads never mutate cache state.
- CLI rejects negative values for `--journal-max` and `--fixture-counts-max` with a clear error (previously silently treated as unbounded). **Breaking for anyone passing `-1` expecting unbounded** — see note above.

### Changed

- `createServer()` programmatic default: `journalMaxEntries` and `fixtureCountsMaxTestIds` now default to finite caps (1000 / 500) instead of unbounded. Long-running embedders that relied on unbounded retention must now opt in explicitly by passing `0`. Back-compat with test harnesses using `new Journal()` directly is preserved (they still default to unbounded). **Note:** this is a behavior change that in retrospect warranted a MINOR bump rather than PATCH.

### Added

- New `--fixture-counts-max <n>` CLI flag (default 500) to cap the fixture-match-counts map by testId.

## 1.14.1

### Fixed

- Cap in-memory journal (and fixture-match-counts map) to prevent heap OOM under sustained load. `Journal.entries` was unbounded, causing heap growth ~3.8MB/sec to 4GB → OOM in ~18 minutes on long-running production deployments. Default cap for CLI (`serve`) is now 1000 entries; programmatic `createServer()` remains unbounded by default (back-compat). See `--journal-max` flag.

## 1.14.0

### Added

- Response template merging — override `id`, `created`, `model`, `usage`, `finishReason`, `role`, `systemFingerprint` on fixture responses across all 4 provider formats (OpenAI, Claude, Gemini, Responses API) (#111)
- JSON auto-stringify — fixture `arguments` and `content` fields accept objects that are auto-stringified by the loader, eliminating escaped JSON pain (#111)
- Migration guide from openai-responses-python (#111)
- All fixture examples and docs converted to object syntax (#111)
- `ResponseOverrides` field validation in `validateFixtures` — catches invalid types for `id`, `created`, `model`, `usage`, `finishReason`, `role`, `systemFingerprint`

### Fixed

- `onTranscription` docs now show correct 1-argument signature
- `validateFixtures` now recognizes ContentWithToolCalls and multimedia response types

## 1.13.0

### Added

- GitHub Action for one-line CI setup — `uses: CopilotKit/aimock@v1` with fixtures, config, port, args, and health check (#102)
- Fixture converters wired into the CLI — `npx @copilotkit/aimock convert vidaimock` and `npx @copilotkit/aimock convert mockllm` as first-class subcommands (#102)
- 30 npm keywords for search discoverability (#102)
- Fixture gallery with 11 examples covering all mock types, plus browsable docs page at /examples (#102)
- Vitest and jest plugins for zero-config testing — `import { useAimock } from "@copilotkit/aimock/vitest"` (#102)

### Changed

- Strip video URLs from README for npm publishing (#102)

## 1.12.0

### Added

- Multimedia endpoint support: image generation (OpenAI DALL-E + Gemini Imagen), text-to-speech, audio transcription, and video generation with async polling (#101)
- `match.endpoint` field for fixture isolation — prevents cross-matching between chat, image, speech, transcription, video, and embedding fixtures (#101)
- Bidirectional endpoint filtering — generic fixtures only match compatible endpoint types (#101)
- Convenience methods: `onImage`, `onSpeech`, `onTranscription`, `onVideo` (#101)
- Record & replay for all multimedia endpoints — proxy to real APIs, save fixtures with correct format/type detection (#101)
- `_endpointType` explicit field on `ChatCompletionRequest` for type safety (#101)
- Comparison matrix and drift detection rules updated for multimedia (#101)
- 54 new tests (32 integration, 11 record/replay, 12 type/routing)

## 1.11.0

### Added

- `AGUIMock` — mock the AG-UI (Agent-to-UI) protocol for CopilotKit frontend testing. All 33 event types, 11 convenience builders, fluent registration API, SSE streaming with disconnect handling (#100)
- AG-UI record & replay with tee streaming — proxy to real AG-UI agents, record event streams as fixtures, replay on subsequent requests. Includes `--proxy-only` mode for demos (#100)
- AG-UI schema drift detection — compares aimock event types against canonical `@ag-ui/core` Zod schemas to catch protocol changes (#100)
- `--agui-record`, `--agui-upstream`, `--agui-proxy-only` CLI flags (#100)

### Removed

- Section bar from docs pages (cleanup)

## 1.10.0

### Added

- `--proxy-only` flag — proxy unmatched requests to upstream providers without saving fixtures to disk or caching in memory. Every unmatched request always hits the real provider, preventing stale recorded responses in demo/live environments (#99)

## 1.9.0

### Added

- Per-test sequence isolation via `X-Test-Id` header — each test gets its own fixture match counters, wired through all 12 HTTP handlers and 3 WebSocket handlers. No more test pollution from shared sequential state (#93)
- Combined `content + toolCalls` in fixture responses — new `ContentWithToolCallsResponse` type and type guard, supported across OpenAI Chat, OpenAI Responses, Anthropic Messages, and Gemini, with stream collapse support (#92)
- OpenRouter `reasoning_content` support in chat completions (#88)
- Demo video in README (#91)
- CI: Slack notifications for drift tests, competitive matrix updates, and new PRs (#86)
- Docs: reasoning and webSearches rows in Response Types table

### Fixed

- `web_search_call` items now use `action.query` matching real OpenAI API format (#89)
- Homepage URL cleaned up (remove `/index.html` suffix) (#90)
- Record & Replay section title now centered and terminal panel top-aligned (#87)
- CI: use `pull_request_target` for fork PR Slack alerts

## 1.8.0

### Added

- `requestTransform` option for deterministic matching and recording — normalizes requests before matching (strips timestamps, UUIDs, session IDs) and switches to exact equality when set. Applied across all 15 provider handlers and the recorder. (#79, based on design by @iskhakovt in #63)
- Reasoning/thinking support for OpenAI Chat Completions — `reasoning` field in fixtures generates `reasoning_content` in responses and streaming `reasoning` deltas (#62 by @erezcor)
- Reasoning support for Gemini (`thoughtParts`), AWS Bedrock InvokeModel + Converse (`thinking` blocks), and Ollama (`think` tags) (#81)
- Web search result events for OpenAI Responses API (#62)
- Open Graph image and meta tags for social sharing
- CI: `npm` environment to release workflow for deployment tracking; `workflow_dispatch` added to Python test workflow

### Changed

- Updated all GitHub repo URLs from CopilotKit/llmock to CopilotKit/aimock
- Reframed drift detection docs for users ("your mocks never go stale") with restored drift report output

### Fixed

- Migration page examples: replaced fragile `time.sleep` with health check loops against `/__aimock/health`; fixed Python npx example `stderr=subprocess.PIPE` deadlock (#80)
- Stream collapse now handles reasoning events correctly

## 1.7.0

### Added

- MCPMock — Model Context Protocol mock with tools, resources, prompts, session management
- A2AMock — Agent-to-Agent protocol mock with SSE streaming
- VectorMock — Pinecone, Qdrant, ChromaDB compatible vector DB mock
- Search (Tavily), rerank (Cohere), and moderation (OpenAI) service mocks
- `/__aimock/*` control API for external fixture management
- `aimock` CLI with JSON config file support
- Mount composition for running multiple protocol handlers on one server
- JSON-RPC 2.0 transport with batch and notifications
- `aimock-pytest` pip package for native Python testing
- Converter scripts: `convert-vidaimock` (Tera → JSON) and `convert-mockllm` (YAML → JSON)
- Drift automation skill updates — `fix-drift.ts` now updates `skills/write-fixtures/SKILL.md` alongside source fixes
- Docker: dual-push `ghcr.io/copilotkit/aimock` + `ghcr.io/copilotkit/llmock` (compat)
- 6 migration guides: MSW, VidaiMock, mock-llm, piyook, Python mocks, Mokksy
- Docs: sidebar.js, cli-tabs.js, section bar, competitive matrix with 25 rows

### Changed

- Renamed package from `@copilotkit/llmock` to `@copilotkit/aimock`
- Renamed Prometheus metrics to `aimock_*` with new MCP/A2A/Vector counters
- Rebranded logger `[aimock]`, chaos headers `x-aimock-chaos-*`, CLI startup message
- Helm chart renamed to `charts/aimock/`
- Homepage redesigned (Treatment 3: Progressive Disclosure)

## 1.6.1

### Fixed

- Record proxy now preserves upstream URL path prefixes — base URLs like `https://gateway.company.com/llm` now correctly resolve to `gateway.company.com/llm/v1/chat/completions` instead of losing the `/llm` prefix (PR #57)
- Record proxy now forwards all request headers to upstream, not just `Content-Type` and auth headers. Hop-by-hop headers (`connection`, `keep-alive`, `transfer-encoding`, etc.) and client-set headers (`host`, `content-length`, `cookie`, `accept-encoding`) are still stripped (PR #58)
- Recorder now decodes base64-encoded embeddings when `encoding_format: "base64"` is set in the request. Python's openai SDK uses this by default. Previously these were saved as `proxy_error` fixtures (PR #64)
- Guarded base64 embedding decode against corrupted data (non-float32-aligned buffers fall through gracefully instead of crashing)

### Added

- `--summary` flag on the competitive matrix update script for markdown-formatted change summaries

## 1.6.0

### Added

- Provider-specific endpoints: dedicated routes for Bedrock (`/model/{modelId}/invoke`), Ollama (`/api/chat`, `/api/generate`), Cohere (`/v2/chat`), and Azure OpenAI deployment-based routing (`/openai/deployments/{id}/chat/completions`)
- Chaos injection: `ChaosConfig` type with `drop`, `malformed`, and `disconnect` actions; supports per-fixture chaos via `chaos` config on each fixture and server-wide chaos via `--chaos-drop`, `--chaos-malformed`, and `--chaos-disconnect` CLI flags
- Metrics: `GET /metrics` endpoint exposing Prometheus text format with request counters and latency histograms per provider and route
- Record-and-replay: `--record` flag and `proxyAndRecord` helper that proxies requests to real LLM APIs, collapses streaming responses, and writes fixture JSON to disk for future playback

## 1.5.1

### Fixed

- Documentation URLs now use the correct domain (llmock.copilotkit.dev)

## 1.5.0

### Added

- Embeddings API: `POST /v1/embeddings` endpoint, `onEmbedding()` convenience method, `inputText` match field, `EmbeddingResponse` type, deterministic fallback embeddings from input hash, Azure embedding routing
- Structured output / JSON mode: `responseFormat` match field, `onJsonOutput()` convenience method
- Sequential responses: `sequenceIndex` match field for stateful multi-turn fixtures, per-fixture-group match counting, `resetMatchCounts()` method
- Streaming physics: `StreamingProfile` type with `ttft`, `tps`, `jitter` fields for realistic timing simulation
- AWS Bedrock: `POST /model/{modelId}/invoke` endpoint, Anthropic Messages format translation
- Azure OpenAI: provider routing for `/openai/deployments/{id}/chat/completions` and `/openai/deployments/{id}/embeddings`
- Health & models endpoints: `GET /health`, `GET /ready`, `GET /v1/models` (auto-populated from fixtures)
- Docker & Helm: Dockerfile, Helm chart for Kubernetes deployment
- Documentation website: full docs site at llmock.copilotkit.dev with feature pages and competitive comparison matrix
- Automated drift remediation: `scripts/drift-report-collector.ts` and `scripts/fix-drift.ts` for CI-driven drift fixes
- CI automation: competitive matrix update workflow, drift fix workflow
- `FixtureOpts` and `EmbeddingFixtureOpts` type aliases exported for external consumers
- `.worktrees/` to eslint ignores

### Changed

- Default to non-streaming for Claude Messages API and Responses API (matching real API defaults)
- README rewritten as concise overview with links to docs site
- Write-fixtures skill updated for all v1.5.0 features
- Docs site: Get Started links to docs, comparison above reliability, npm version badge

### Fixed

- Gemini Live handler no longer crashes on malformed `clientContent.turns` and `toolResponse.functionResponses`
- Added `isClosed` guard before WebSocket finalization events (prevents writes to closed connections)
- `streamingProfile` now present on convenience method opts types (`on`, `onMessage`, etc.)
- skills/ symlink direction corrected so `npm pack` includes the write-fixtures skill
- `.claude` removed from package.json files (was dead weight — symlink doesn't ship)
- Watcher cleanup on error (clear debounce timer, null guard)
- Empty-reload guard (keep previous fixtures when reload produces 0)

### Removed

- Dead `@keyframes sseLine` CSS from docs site

## 1.4.0

### Added

- `--watch` (`-w`): File-watching with 500ms debounced reload. Keeps previous fixtures on validation failure.
- `--log-level`: Configurable log verbosity (`silent`, `info`, `debug`). Default `info` for CLI, `silent` for programmatic API.
- `--validate-on-load`: Fixture schema validation at startup — checks response types, tool call JSON, numeric ranges, shadowing, and catch-all positioning.
- `validateFixtures()` exported for programmatic use
- `Logger` class exported for programmatic use

## 1.3.3

### Added

- WebSocket drift detection tests: TLS client for real provider WS endpoints, 4 verified drift tests (Responses WS + Realtime), Gemini Live canary for text-capable model availability
- Realtime model canary: detects when `gpt-4o-mini-realtime-preview` is deprecated and suggests GA replacement
- Gemini Live documented as unverified (no text-capable `bidiGenerateContent` model exists yet)

### Fixed

- Responses WS handler now accepts flat `response.create` format matching the real OpenAI API (previously required a non-standard nested `response: { ... }` envelope)
- README Gemini Live response shape example corrected (`modelTurn.parts`, not `modelTurnComplete`)

## 1.3.2

### Added

- Live API drift detection test suite: three-layer triangulation between SDK types, real API responses, and llmock output across OpenAI (Chat + Responses), Anthropic Claude, and Google Gemini
- Weekly CI workflow for automated drift checks
- `DRIFT.md` documentation for the drift detection system

### Fixed

- Missing `refusal` field on OpenAI Chat Completions responses — both the SDK and real API return `refusal: null` on non-refusal messages, but llmock was omitting it

## 1.3.1

### Added

- Claude Code fixture authoring skill (`/write-fixtures`) — comprehensive guide for match fields, response types, agent loop patterns, gotchas, and debugging
- Claude Code plugin structure for downstream consumers (`--plugin-dir`, `--add-dir`, or manual copy)

### Changed

- README and docs site updated with Claude Code integration instructions

## 1.3.0

### Added

- Mid-stream interruption: `truncateAfterChunks` and `disconnectAfterMs` fixture fields to simulate abrupt server disconnects
- AbortSignal-based cancellation primitives (`createInterruptionSignal`, signal-aware `delay()`)
- Backward-compatible `writeSSEStream` overload with `StreamOptions` returning completion status
- Interruption support across all HTTP SSE and WebSocket streaming paths
- `destroy()` method on `WebSocketConnection` for abrupt disconnect simulation
- Journal records `interrupted` and `interruptReason` on interrupted streams
- LLMock convenience API extended with interruption options (`truncateAfterChunks`, `disconnectAfterMs`)

## 1.2.0

### Added

- Zero-dependency RFC 6455 WebSocket framing layer
- OpenAI Responses API over WebSocket (`/v1/responses`)
- OpenAI Realtime API over WebSocket (`/v1/realtime`) — text + tool calls
- Gemini Live BidiGenerateContent over WebSocket — text + tool calls
- Future Direction section in README

### Fixed

- WebSocket close-frame lifecycle
- Improved error visibility across WebSocket handlers

## 1.1.1

### Added

- Function call IDs on Gemini tool call responses

### Removed

- Changesets (simplified release workflow)

## 1.1.0

### Added

- 9948a8b: `prependFixture()` and `getFixtures()` public API methods

## 1.0.1

### Added

- `getTextContent` for array-format message content handling
