@copilotkit/aimock 1.13.0 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +4 -4
  3. package/README.md +12 -7
  4. package/dist/cli.cjs +13 -2
  5. package/dist/cli.cjs.map +1 -1
  6. package/dist/cli.js +13 -2
  7. package/dist/cli.js.map +1 -1
  8. package/dist/config-loader.d.cts.map +1 -1
  9. package/dist/fixture-loader.cjs +131 -29
  10. package/dist/fixture-loader.cjs.map +1 -1
  11. package/dist/fixture-loader.d.cts +9 -2
  12. package/dist/fixture-loader.d.cts.map +1 -1
  13. package/dist/fixture-loader.d.ts +9 -2
  14. package/dist/fixture-loader.d.ts.map +1 -1
  15. package/dist/fixture-loader.js +132 -31
  16. package/dist/fixture-loader.js.map +1 -1
  17. package/dist/gemini.cjs +76 -55
  18. package/dist/gemini.cjs.map +1 -1
  19. package/dist/gemini.d.cts.map +1 -1
  20. package/dist/gemini.d.ts.map +1 -1
  21. package/dist/gemini.js +77 -56
  22. package/dist/gemini.js.map +1 -1
  23. package/dist/helpers.cjs +142 -76
  24. package/dist/helpers.cjs.map +1 -1
  25. package/dist/helpers.d.cts +14 -4
  26. package/dist/helpers.d.cts.map +1 -1
  27. package/dist/helpers.d.ts +14 -4
  28. package/dist/helpers.d.ts.map +1 -1
  29. package/dist/helpers.js +142 -77
  30. package/dist/helpers.js.map +1 -1
  31. package/dist/index.cjs +10 -0
  32. package/dist/index.d.cts +4 -4
  33. package/dist/index.d.ts +4 -4
  34. package/dist/index.js +3 -3
  35. package/dist/journal.cjs +6 -0
  36. package/dist/journal.cjs.map +1 -1
  37. package/dist/journal.d.cts +15 -1
  38. package/dist/journal.d.cts.map +1 -1
  39. package/dist/journal.d.ts +15 -1
  40. package/dist/journal.d.ts.map +1 -1
  41. package/dist/journal.js +6 -0
  42. package/dist/journal.js.map +1 -1
  43. package/dist/llmock.cjs +1 -1
  44. package/dist/llmock.cjs.map +1 -1
  45. package/dist/llmock.d.cts +6 -6
  46. package/dist/llmock.d.cts.map +1 -1
  47. package/dist/llmock.d.ts +6 -6
  48. package/dist/llmock.d.ts.map +1 -1
  49. package/dist/llmock.js +2 -2
  50. package/dist/llmock.js.map +1 -1
  51. package/dist/messages.cjs +69 -63
  52. package/dist/messages.cjs.map +1 -1
  53. package/dist/messages.d.cts.map +1 -1
  54. package/dist/messages.d.ts.map +1 -1
  55. package/dist/messages.js +70 -64
  56. package/dist/messages.js.map +1 -1
  57. package/dist/recorder.cjs +1 -1
  58. package/dist/recorder.cjs.map +1 -1
  59. package/dist/recorder.js +1 -1
  60. package/dist/recorder.js.map +1 -1
  61. package/dist/responses.cjs +66 -57
  62. package/dist/responses.cjs.map +1 -1
  63. package/dist/responses.d.cts +3 -3
  64. package/dist/responses.d.cts.map +1 -1
  65. package/dist/responses.d.ts +3 -3
  66. package/dist/responses.d.ts.map +1 -1
  67. package/dist/responses.js +67 -58
  68. package/dist/responses.js.map +1 -1
  69. package/dist/server.cjs +58 -31
  70. package/dist/server.cjs.map +1 -1
  71. package/dist/server.d.cts.map +1 -1
  72. package/dist/server.d.ts.map +1 -1
  73. package/dist/server.js +59 -32
  74. package/dist/server.js.map +1 -1
  75. package/dist/stream-collapse.cjs.map +1 -1
  76. package/dist/stream-collapse.d.cts.map +1 -1
  77. package/dist/stream-collapse.d.ts.map +1 -1
  78. package/dist/stream-collapse.js.map +1 -1
  79. package/dist/types.d.cts +74 -11
  80. package/dist/types.d.cts.map +1 -1
  81. package/dist/types.d.ts +74 -11
  82. package/dist/types.d.ts.map +1 -1
  83. package/dist/vector-types.d.ts.map +1 -1
  84. package/fixtures/example-multi-turn.json +1 -1
  85. package/fixtures/example-tool-call.json +1 -1
  86. package/fixtures/examples/adk/gemini-agent.json +47 -0
  87. package/fixtures/examples/crewai/multi-agent-crew.json +16 -0
  88. package/fixtures/examples/langchain/agent-loop.json +27 -0
  89. package/fixtures/examples/llamaindex/aimock-config.json +62 -0
  90. package/fixtures/examples/llamaindex/rag-pipeline.json +34 -0
  91. package/fixtures/examples/mastra/agent-workflow.json +32 -0
  92. package/fixtures/examples/pydanticai/structured-output.json +15 -0
  93. package/package.json +2 -1
  94. package/skills/write-fixtures/SKILL.md +148 -22
@@ -0,0 +1,27 @@
1
+ {
2
+ "fixtures": [
3
+ {
4
+ "match": { "userMessage": "plan a trip", "sequenceIndex": 0 },
5
+ "response": {
6
+ "content": "I'll help plan your trip. Let me look up some options."
7
+ }
8
+ },
9
+ {
10
+ "match": { "userMessage": "plan a trip", "sequenceIndex": 1 },
11
+ "response": {
12
+ "toolCalls": [
13
+ {
14
+ "name": "search_flights",
15
+ "arguments": { "origin": "SFO", "dest": "NRT" }
16
+ }
17
+ ]
18
+ }
19
+ },
20
+ {
21
+ "match": { "userMessage": "plan a trip", "sequenceIndex": 2 },
22
+ "response": {
23
+ "content": "I found 3 flights from SFO to Tokyo Narita. The best option is..."
24
+ }
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,62 @@
1
+ {
2
+ "llm": {
3
+ "fixtures": "./rag-pipeline.json"
4
+ },
5
+ "vector": {
6
+ "collections": [
7
+ {
8
+ "name": "knowledge-base",
9
+ "dimension": 3,
10
+ "vectors": [
11
+ {
12
+ "id": "doc-gravity",
13
+ "values": [0.9, 0.1, 0.05],
14
+ "metadata": {
15
+ "source": "physics.pdf",
16
+ "page": 12,
17
+ "text": "Gravity is a fundamental force of nature that attracts objects with mass toward one another."
18
+ }
19
+ },
20
+ {
21
+ "id": "doc-orbits",
22
+ "values": [0.75, 0.3, 0.15],
23
+ "metadata": {
24
+ "source": "physics.pdf",
25
+ "page": 45,
26
+ "text": "Orbital mechanics describes the motion of planets and satellites under gravitational influence."
27
+ }
28
+ },
29
+ {
30
+ "id": "doc-tides",
31
+ "values": [0.6, 0.5, 0.2],
32
+ "metadata": {
33
+ "source": "physics.pdf",
34
+ "page": 78,
35
+ "text": "Tidal forces result from the differential gravitational pull of the Moon and Sun on Earth's oceans."
36
+ }
37
+ }
38
+ ],
39
+ "queryResults": [
40
+ {
41
+ "id": "doc-gravity",
42
+ "score": 0.97,
43
+ "metadata": {
44
+ "source": "physics.pdf",
45
+ "page": 12,
46
+ "text": "Gravity is a fundamental force of nature that attracts objects with mass toward one another."
47
+ }
48
+ },
49
+ {
50
+ "id": "doc-orbits",
51
+ "score": 0.82,
52
+ "metadata": {
53
+ "source": "physics.pdf",
54
+ "page": 45,
55
+ "text": "Orbital mechanics describes the motion of planets and satellites under gravitational influence."
56
+ }
57
+ }
58
+ ]
59
+ }
60
+ ]
61
+ }
62
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "fixtures": [
3
+ {
4
+ "match": { "userMessage": "What is gravity?" },
5
+ "response": {
6
+ "content": "Based on the retrieved documents, gravity is a fundamental force of nature that attracts objects with mass toward one another. It is described by Newton's law of universal gravitation and Einstein's general theory of relativity."
7
+ }
8
+ },
9
+ {
10
+ "match": { "userMessage": "Summarize the document" },
11
+ "response": {
12
+ "content": "The document covers three main topics: gravitational force, orbital mechanics, and tidal effects. It explains how gravity governs planetary motion and influences ocean tides on Earth."
13
+ }
14
+ },
15
+ {
16
+ "match": { "inputText": "What is gravity?", "endpoint": "embedding" },
17
+ "response": {
18
+ "embedding": [0.9, 0.1, 0.05]
19
+ }
20
+ },
21
+ {
22
+ "match": { "inputText": "Gravity is a fundamental force", "endpoint": "embedding" },
23
+ "response": {
24
+ "embedding": [0.88, 0.12, 0.07]
25
+ }
26
+ },
27
+ {
28
+ "match": { "inputText": "orbital mechanics and planetary motion", "endpoint": "embedding" },
29
+ "response": {
30
+ "embedding": [0.75, 0.3, 0.15]
31
+ }
32
+ }
33
+ ]
34
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "fixtures": [
3
+ {
4
+ "match": { "userMessage": "plan a trip", "sequenceIndex": 0 },
5
+ "response": {
6
+ "toolCalls": [
7
+ {
8
+ "name": "search_flights",
9
+ "arguments": { "origin": "SFO", "destination": "NRT", "date": "2025-03-15" }
10
+ }
11
+ ]
12
+ }
13
+ },
14
+ {
15
+ "match": { "userMessage": "plan a trip", "sequenceIndex": 1 },
16
+ "response": {
17
+ "toolCalls": [
18
+ {
19
+ "name": "search_hotels",
20
+ "arguments": { "city": "Tokyo", "checkIn": "2025-03-15", "checkOut": "2025-03-22" }
21
+ }
22
+ ]
23
+ }
24
+ },
25
+ {
26
+ "match": { "userMessage": "plan a trip", "sequenceIndex": 2 },
27
+ "response": {
28
+ "content": "I found a great itinerary for your Tokyo trip!\n\n**Flight:** SFO → NRT on March 15, departing 11:30 AM (United UA837) — $890 round trip\n\n**Hotel:** Hotel Gracery Shinjuku, March 15–22 — $185/night\n\nWould you like me to book these, or would you prefer different options?"
29
+ }
30
+ }
31
+ ]
32
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "fixtures": [
3
+ {
4
+ "match": { "userMessage": "Weather" },
5
+ "response": {
6
+ "toolCalls": [
7
+ {
8
+ "name": "final_result",
9
+ "arguments": { "city": "SF", "temp": 72, "unit": "fahrenheit" }
10
+ }
11
+ ]
12
+ }
13
+ }
14
+ ]
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@copilotkit/aimock",
3
- "version": "1.13.0",
3
+ "version": "1.14.1",
4
4
  "description": "Mock infrastructure for AI application testing — LLM APIs, image generation, text-to-speech, transcription, video generation, MCP tools, A2A agents, AG-UI event streams, vector databases, search, rerank, and moderation. One package, one port, zero dependencies.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -35,6 +35,7 @@
35
35
  "embeddings",
36
36
  "copilotkit"
37
37
  ],
38
+ "homepage": "https://aimock.copilotkit.dev",
38
39
  "repository": {
39
40
  "type": "git",
40
41
  "url": "https://github.com/CopilotKit/aimock"
@@ -7,7 +7,7 @@ description: Use when writing test fixtures for @copilotkit/aimock — mock LLM
7
7
 
8
8
  ## What aimock Is
9
9
 
10
- aimock is a zero-dependency mock LLM server. Fixture-driven. Multi-provider (OpenAI, Anthropic, Gemini, AWS Bedrock, Azure OpenAI, Vertex AI, Ollama, Cohere). Runs a real HTTP server on a real port — works across processes, unlike MSW-style interceptors. WebSocket support for OpenAI Responses/Realtime and Gemini Live APIs. Chaos testing and Prometheus metrics.
10
+ aimock is a zero-dependency mock infrastructure for AI apps. Fixture-driven. Multi-provider (OpenAI, Anthropic, Gemini, AWS Bedrock, Azure OpenAI, Vertex AI, Ollama, Cohere). Multimedia endpoints (image generation, text-to-speech, audio transcription, video generation). MCP, A2A, AG-UI, and vector DB mocking. Runs a real HTTP server on a real port — works across processes, unlike MSW-style interceptors. WebSocket support for OpenAI Responses/Realtime and Gemini Live APIs. Record-and-replay for all endpoints including multimedia. Chaos testing and Prometheus metrics.
11
11
 
12
12
  ## Core Mental Model
13
13
 
@@ -19,19 +19,20 @@ aimock is a zero-dependency mock LLM server. Fixture-driven. Multi-provider (Ope
19
19
 
20
20
  ## Match Field Reference
21
21
 
22
- | Field | Type | Matches Against |
23
- | ---------------- | ----------------------------------------- | ----------------------------------------------------------------------------- |
24
- | `userMessage` | `string` | Substring of last `role: "user"` message text |
25
- | `userMessage` | `RegExp` | Pattern test on last `role: "user"` message text |
26
- | `inputText` | `string` | Substring of embedding input text (concatenated if multiple inputs) |
27
- | `inputText` | `RegExp` | Pattern test on embedding input text |
28
- | `toolName` | `string` | Exact match on any tool in request's `tools[]` array (by `function.name`) |
29
- | `toolCallId` | `string` | Exact match on `tool_call_id` of last `role: "tool"` message |
30
- | `model` | `string` | Exact match on `req.model` |
31
- | `model` | `RegExp` | Pattern test on `req.model` |
32
- | `responseFormat` | `string` | Exact match on `req.response_format.type` (`"json_object"`, `"json_schema"`) |
33
- | `sequenceIndex` | `number` | Matches only when this fixture's match count equals the given index (0-based) |
34
- | `predicate` | `(req: ChatCompletionRequest) => boolean` | Custom function full access to request |
22
+ | Field | Type | Matches Against |
23
+ | ---------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------- |
24
+ | `userMessage` | `string` | Substring of last `role: "user"` message text |
25
+ | `userMessage` | `RegExp` | Pattern test on last `role: "user"` message text |
26
+ | `inputText` | `string` | Substring of embedding input text (concatenated if multiple inputs) |
27
+ | `inputText` | `RegExp` | Pattern test on embedding input text |
28
+ | `toolName` | `string` | Exact match on any tool in request's `tools[]` array (by `function.name`) |
29
+ | `toolCallId` | `string` | Exact match on `tool_call_id` of last `role: "tool"` message |
30
+ | `model` | `string` | Exact match on `req.model` |
31
+ | `model` | `RegExp` | Pattern test on `req.model` |
32
+ | `responseFormat` | `string` | Exact match on `req.response_format.type` (`"json_object"`, `"json_schema"`) |
33
+ | `sequenceIndex` | `number` | Matches only when this fixture's match count equals the given index (0-based) |
34
+ | `endpoint` | `string` | Restrict to endpoint type: `"chat"`, `"image"`, `"speech"`, `"transcription"`, `"video"`, `"embedding"` |
35
+ | `predicate` | `(req: ChatCompletionRequest) => boolean` | Custom function — full access to request |
35
36
 
36
37
  **AND logic**: all specified fields must match. Empty match `{}` = catch-all.
37
38
 
@@ -50,12 +51,18 @@ Multi-part content (e.g., `[{type: "text", text: "hello"}]`) is automatically ex
50
51
  ### Tool Calls
51
52
 
52
53
  ```typescript
54
+ // Preferred: object form (auto-stringified by the fixture loader)
55
+ {
56
+ toolCalls: [{ name: "get_weather", arguments: { city: "SF" } }];
57
+ }
58
+
59
+ // Also accepted: JSON string form (backward compatible)
53
60
  {
54
61
  toolCalls: [{ name: "get_weather", arguments: '{"city":"SF"}' }];
55
62
  }
56
63
  ```
57
64
 
58
- **`arguments` MUST be a JSON string**, not an object. This is the #1 mistake.
65
+ **Both object and string forms are accepted** for `arguments`. The fixture loader auto-stringifies objects via `JSON.stringify()`. Object form is preferred for readability.
59
66
 
60
67
  ### Embedding
61
68
 
@@ -67,6 +74,49 @@ Multi-part content (e.g., `[{type: "text", text: "hello"}]`) is automatically ex
67
74
 
68
75
  The embedding vector is returned for each input in the request. If no embedding fixture matches, deterministic embeddings are auto-generated from the input text hash — you only need fixtures when you want specific vectors.
69
76
 
77
+ ### Image
78
+
79
+ <!-- prettier-ignore -->
80
+ ```typescript
81
+ // Single image
82
+ {
83
+ image: {
84
+ url: "https://example.com/generated.png"
85
+ }
86
+ }
87
+ // Multiple images
88
+ {
89
+ images: [{ url: "https://example.com/1.png" }, { b64Json: "iVBOR..." }]
90
+ }
91
+ ```
92
+
93
+ Use `match: { endpoint: "image" }` to prevent cross-matching with chat fixtures.
94
+
95
+ ### Speech (TTS)
96
+
97
+ ```typescript
98
+ { audio: "base64-encoded-audio-data" }
99
+ // With explicit format (default: mp3)
100
+ { audio: "base64-data", format: "opus" }
101
+ ```
102
+
103
+ ### Transcription
104
+
105
+ ```typescript
106
+ // Simple
107
+ { transcription: { text: "Hello world" } }
108
+ // Verbose with timestamps
109
+ { transcription: { text: "Hello world", language: "en", duration: 2.5, words: [...], segments: [...] } }
110
+ ```
111
+
112
+ ### Video
113
+
114
+ ```typescript
115
+ { video: { id: "vid-1", status: "completed", url: "https://example.com/video.mp4" } }
116
+ ```
117
+
118
+ Video uses async polling — `POST /v1/videos` creates, `GET /v1/videos/{id}` checks status.
119
+
70
120
  ### Error
71
121
 
72
122
  ```typescript
@@ -104,7 +154,7 @@ The most common pattern. Fixture 1 triggers the tool call, fixture 2 handles the
104
154
  ```typescript
105
155
  // Step 1: User asks about weather → LLM calls tool
106
156
  mock.onMessage("weather", {
107
- toolCalls: [{ name: "get_weather", arguments: '{"city":"SF"}' }],
157
+ toolCalls: [{ name: "get_weather", arguments: { city: "SF" } }],
108
158
  });
109
159
 
110
160
  // Step 2: Tool result comes back → LLM responds with text
@@ -154,7 +204,7 @@ mock.addFixture({
154
204
  // First call returns tool call, second returns text
155
205
  mock.on(
156
206
  { userMessage: "status", sequenceIndex: 0 },
157
- { toolCalls: [{ name: "check_status", arguments: "{}" }] },
207
+ { toolCalls: [{ name: "check_status", arguments: {} }] },
158
208
  );
159
209
  mock.on({ userMessage: "status", sequenceIndex: 1 }, { content: "All systems operational." });
160
210
  ```
@@ -189,7 +239,7 @@ mock.addFixture({
189
239
  return typeof sys === "string" && sys.includes("Flights found: false");
190
240
  },
191
241
  },
192
- response: { toolCalls: [{ name: "search_flights", arguments: "{}" }] },
242
+ response: { toolCalls: [{ name: "search_flights", arguments: {} }] },
193
243
  });
194
244
  ```
195
245
 
@@ -263,6 +313,17 @@ mock.nextRequestError(429, { message: "Rate limited", type: "rate_limit_error" }
263
313
  "match": { "userMessage": "hello" },
264
314
  "response": { "content": "Hi!" }
265
315
  },
316
+ {
317
+ "match": { "userMessage": "weather" },
318
+ "response": {
319
+ "toolCalls": [
320
+ {
321
+ "name": "get_weather",
322
+ "arguments": { "city": "SF", "units": "fahrenheit" }
323
+ }
324
+ ]
325
+ }
326
+ },
266
327
  {
267
328
  "match": { "inputText": "search query" },
268
329
  "response": { "embedding": [0.1, 0.2, 0.3] }
@@ -275,6 +336,8 @@ mock.nextRequestError(429, { message: "Rate limited", type: "rate_limit_error" }
275
336
  }
276
337
  ```
277
338
 
339
+ **JSON auto-stringify**: In JSON fixture files, `arguments` and `content` can be objects — the loader auto-stringifies them with `JSON.stringify()`. The escaped-string form (`"{\"city\":\"SF\"}"`) still works but objects are preferred for readability.
340
+
278
341
  JSON files cannot use `RegExp` or `predicate` — those are code-only features. `streamingProfile` is supported in JSON fixture files.
279
342
 
280
343
  Load with `mock.loadFixtureFile("./fixtures/greetings.json")` or `mock.loadFixtureDir("./fixtures/")`.
@@ -309,12 +372,71 @@ All providers share the same fixture pool — write fixtures once, they work for
309
372
  | `WS /v1/responses` | OpenAI | WebSocket |
310
373
  | `WS /v1/realtime` | OpenAI | WebSocket |
311
374
  | `WS /ws/google.ai...BidiGenerateContent` | Gemini Live | WebSocket |
375
+ | `POST /v1/images/generations` | OpenAI | HTTP |
376
+ | `POST /v1beta/models/{model}:predict` | Gemini Imagen | HTTP |
377
+ | `POST /v1/audio/speech` | OpenAI | HTTP |
378
+ | `POST /v1/audio/transcriptions` | OpenAI | HTTP |
379
+ | `POST /v1/videos` | OpenAI | HTTP |
380
+ | `GET /v1/videos/{id}` | OpenAI | HTTP |
381
+
382
+ ## Response Template Overrides
383
+
384
+ Fixture responses can include optional override fields to control auto-generated envelope values. These are merged into the provider-specific response format (OpenAI, Claude, Gemini, Responses API).
385
+
386
+ | Field | Type | Default | Description |
387
+ | ------------------- | ------ | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
388
+ | `id` | string | auto-generated | Override response ID (e.g., `chatcmpl-custom`) |
389
+ | `created` | number | `Date.now()/1000` | Override Unix timestamp |
390
+ | `model` | string | echoes request | Override model name in response |
391
+ | `usage` | object | zeroed | Override token counts: `{ prompt_tokens, completion_tokens, total_tokens }`. OpenAI Chat includes usage in response body; Responses API uses `response.usage`. When omitted, auto-computed from content length |
392
+ | `finishReason` | string | `"stop"` / `"tool_calls"` | Override finish reason. Mappings: `stop` -> `end_turn` (Claude), `STOP` (Gemini); `tool_calls` -> `tool_use` (Claude), `FUNCTION_CALL` (Gemini); `length` -> `max_tokens` (Claude), `MAX_TOKENS` (Gemini); `content_filter` -> `SAFETY` (Gemini), `failed` (Responses API) |
393
+ | `role` | string | `"assistant"` | Override message role |
394
+ | `systemFingerprint` | string | (omitted) | Add `system_fingerprint` to response |
395
+
396
+ ### Example
397
+
398
+ ```typescript
399
+ mock.onMessage("hello", {
400
+ content: "Hi!",
401
+ model: "gpt-4-turbo-2024-04-09",
402
+ usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
403
+ systemFingerprint: "fp_abc123",
404
+ });
405
+ ```
406
+
407
+ ### In JSON fixtures
408
+
409
+ ```json
410
+ {
411
+ "match": { "userMessage": "hello" },
412
+ "response": {
413
+ "content": "Hi!",
414
+ "model": "gpt-4-turbo-2024-04-09",
415
+ "usage": { "prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15 },
416
+ "systemFingerprint": "fp_abc123"
417
+ }
418
+ }
419
+ ```
420
+
421
+ These fields map correctly across all provider formats — for example, `finishReason: "stop"` becomes `finish_reason: "stop"` in OpenAI, `stop_reason: "end_turn"` in Claude, and `finishReason: "STOP"` in Gemini.
422
+
423
+ ## Provider Support Matrix
424
+
425
+ | Feature | OpenAI Chat | OpenAI Responses | Claude | Gemini | Bedrock | Azure | Ollama | Cohere |
426
+ | -------------------- | ----------- | ---------------- | ------ | ------ | ------- | ----- | ------ | ------ |
427
+ | Text | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
428
+ | Tool Calls | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
429
+ | Content + Tool Calls | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
430
+ | Streaming | SSE | SSE | SSE | SSE | Binary | SSE | NDJSON | SSE |
431
+ | Reasoning | Yes | Yes | Yes | Yes | Yes | Yes | -- | -- |
432
+ | Web Searches | -- | Yes | -- | -- | -- | -- | -- | -- |
433
+ | Response Overrides | Yes | Yes | Yes | Yes | -- | Yes | -- | -- |
312
434
 
313
435
  ## Critical Gotchas
314
436
 
315
437
  1. **Order matters** — first match wins. Specific fixtures before general ones. Use `prependFixture()` to force priority.
316
438
 
317
- 2. **`arguments` must be a JSON string** — `"arguments": "{\"key\":\"value\"}"` not `"arguments": {"key":"value"}`. The type system enforces this but JSON fixtures can get it wrong silently.
439
+ 2. **`arguments` accepts both objects and strings** — `"arguments": {"key":"value"}` (preferred, auto-stringified) or `"arguments": "{\"key\":\"value\"}"` (legacy). The same applies to `content` fields that contain JSON. The fixture loader detects `typeof === "object"` and calls `JSON.stringify()` automatically.
318
440
 
319
441
  3. **Latency is per-chunk, not total** — `latency: 100` means 100ms between each SSE chunk, not 100ms total response time. Similarly, `truncateAfterChunks` and `disconnectAfterMs` are for simulating stream interruptions (added in v1.3.0).
320
442
 
@@ -559,6 +681,10 @@ const mock = await LLMock.create({ port: 0 }); // creates + starts in one call
559
681
  | `onSearch(pattern, results)` | Match search requests by query |
560
682
  | `onRerank(pattern, results)` | Match rerank requests by query |
561
683
  | `onModerate(pattern, result)` | Match moderation requests by input |
684
+ | `onImage(pattern, response)` | Match image generation by prompt |
685
+ | `onSpeech(pattern, response)` | Match TTS by input text |
686
+ | `onTranscription(response)` | Match audio transcription |
687
+ | `onVideo(pattern, response)` | Match video generation by prompt |
562
688
  | `mount(path, handler)` | Mount a Mountable (VectorMock, etc.) |
563
689
  | `url` / `baseUrl` | Server URL (throws if not started) |
564
690
  | `port` | Server port number |
@@ -567,19 +693,19 @@ Sequential responses use `on()` with `sequenceIndex` in the match — there is n
567
693
 
568
694
  ## Record-and-Replay (VCR Mode)
569
695
 
570
- llmock supports a VCR-style record-and-replay workflow: unmatched requests are proxied to real provider APIs, and the responses are saved as standard llmock fixture files for deterministic replay.
696
+ aimock supports a VCR-style record-and-replay workflow for ALL endpoints including multimedia (image, TTS, transcription, video): unmatched requests are proxied to real provider APIs, and the responses are saved as standard aimock fixture files for deterministic replay. Binary TTS responses are base64-encoded with format derived from Content-Type. Multimedia fixtures automatically include `endpoint` in their match criteria for correct routing on replay.
571
697
 
572
698
  ### CLI usage
573
699
 
574
700
  ```bash
575
701
  # Record mode: proxy unmatched requests to real OpenAI and Anthropic APIs
576
- llmock --record \
702
+ aimock --record \
577
703
  --provider-openai https://api.openai.com \
578
704
  --provider-anthropic https://api.anthropic.com \
579
705
  -f ./fixtures
580
706
 
581
707
  # Strict mode: fail on unmatched requests (no proxying, no catch-all 404)
582
- llmock --strict -f ./fixtures
708
+ aimock --strict -f ./fixtures
583
709
  ```
584
710
 
585
711
  - `--record` enables proxy-on-miss. Requires at least one `--provider-*` flag.