@copilotkit/aimock 1.17.0 → 1.19.0

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 (180) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +24 -0
  4. package/dist/agui-types.d.ts.map +1 -1
  5. package/dist/bedrock-converse.cjs +6 -6
  6. package/dist/bedrock-converse.cjs.map +1 -1
  7. package/dist/bedrock-converse.d.cts.map +1 -1
  8. package/dist/bedrock-converse.d.ts.map +1 -1
  9. package/dist/bedrock-converse.js +7 -7
  10. package/dist/bedrock-converse.js.map +1 -1
  11. package/dist/bedrock.cjs +6 -6
  12. package/dist/bedrock.cjs.map +1 -1
  13. package/dist/bedrock.d.cts.map +1 -1
  14. package/dist/bedrock.d.ts.map +1 -1
  15. package/dist/bedrock.js +7 -7
  16. package/dist/bedrock.js.map +1 -1
  17. package/dist/chaos.cjs +35 -9
  18. package/dist/chaos.cjs.map +1 -1
  19. package/dist/chaos.d.cts +17 -2
  20. package/dist/chaos.d.cts.map +1 -1
  21. package/dist/chaos.d.ts +17 -2
  22. package/dist/chaos.d.ts.map +1 -1
  23. package/dist/chaos.js +35 -10
  24. package/dist/chaos.js.map +1 -1
  25. package/dist/cohere.cjs +3 -3
  26. package/dist/cohere.cjs.map +1 -1
  27. package/dist/cohere.d.cts.map +1 -1
  28. package/dist/cohere.d.ts.map +1 -1
  29. package/dist/cohere.js +4 -4
  30. package/dist/cohere.js.map +1 -1
  31. package/dist/config-loader.d.cts.map +1 -1
  32. package/dist/elevenlabs-audio.cjs +6 -3
  33. package/dist/elevenlabs-audio.cjs.map +1 -1
  34. package/dist/elevenlabs-audio.d.cts.map +1 -1
  35. package/dist/elevenlabs-audio.d.ts.map +1 -1
  36. package/dist/elevenlabs-audio.js +7 -4
  37. package/dist/elevenlabs-audio.js.map +1 -1
  38. package/dist/embeddings.cjs +3 -3
  39. package/dist/embeddings.cjs.map +1 -1
  40. package/dist/embeddings.d.cts.map +1 -1
  41. package/dist/embeddings.d.ts.map +1 -1
  42. package/dist/embeddings.js +4 -4
  43. package/dist/embeddings.js.map +1 -1
  44. package/dist/fal-audio.cjs +13 -6
  45. package/dist/fal-audio.cjs.map +1 -1
  46. package/dist/fal-audio.d.cts.map +1 -1
  47. package/dist/fal-audio.d.ts.map +1 -1
  48. package/dist/fal-audio.js +14 -8
  49. package/dist/fal-audio.js.map +1 -1
  50. package/dist/fal.cjs +424 -0
  51. package/dist/fal.cjs.map +1 -0
  52. package/dist/fal.d.cts +39 -0
  53. package/dist/fal.d.cts.map +1 -0
  54. package/dist/fal.d.ts +39 -0
  55. package/dist/fal.d.ts.map +1 -0
  56. package/dist/fal.js +420 -0
  57. package/dist/fal.js.map +1 -0
  58. package/dist/fixture-loader.cjs +128 -126
  59. package/dist/fixture-loader.cjs.map +1 -1
  60. package/dist/fixture-loader.d.cts.map +1 -1
  61. package/dist/fixture-loader.d.ts.map +1 -1
  62. package/dist/fixture-loader.js +129 -127
  63. package/dist/fixture-loader.js.map +1 -1
  64. package/dist/gemini-interactions.cjs +5 -3
  65. package/dist/gemini-interactions.cjs.map +1 -1
  66. package/dist/gemini-interactions.d.cts.map +1 -1
  67. package/dist/gemini-interactions.d.ts.map +1 -1
  68. package/dist/gemini-interactions.js +6 -4
  69. package/dist/gemini-interactions.js.map +1 -1
  70. package/dist/gemini.cjs +3 -3
  71. package/dist/gemini.cjs.map +1 -1
  72. package/dist/gemini.d.cts.map +1 -1
  73. package/dist/gemini.d.ts.map +1 -1
  74. package/dist/gemini.js +4 -4
  75. package/dist/gemini.js.map +1 -1
  76. package/dist/helpers.cjs +29 -0
  77. package/dist/helpers.cjs.map +1 -1
  78. package/dist/helpers.d.cts.map +1 -1
  79. package/dist/helpers.d.ts.map +1 -1
  80. package/dist/helpers.js +27 -1
  81. package/dist/helpers.js.map +1 -1
  82. package/dist/images.cjs +3 -3
  83. package/dist/images.cjs.map +1 -1
  84. package/dist/images.d.cts.map +1 -1
  85. package/dist/images.d.ts.map +1 -1
  86. package/dist/images.js +4 -4
  87. package/dist/images.js.map +1 -1
  88. package/dist/index.cjs +3 -0
  89. package/dist/index.d.cts +3 -2
  90. package/dist/index.d.ts +3 -2
  91. package/dist/index.js +2 -1
  92. package/dist/llmock.cjs +16 -1
  93. package/dist/llmock.cjs.map +1 -1
  94. package/dist/llmock.d.cts +10 -7
  95. package/dist/llmock.d.cts.map +1 -1
  96. package/dist/llmock.d.ts +10 -7
  97. package/dist/llmock.d.ts.map +1 -1
  98. package/dist/llmock.js +16 -1
  99. package/dist/llmock.js.map +1 -1
  100. package/dist/messages.cjs +3 -3
  101. package/dist/messages.cjs.map +1 -1
  102. package/dist/messages.d.cts.map +1 -1
  103. package/dist/messages.d.ts.map +1 -1
  104. package/dist/messages.js +4 -4
  105. package/dist/messages.js.map +1 -1
  106. package/dist/ollama.cjs +6 -6
  107. package/dist/ollama.cjs.map +1 -1
  108. package/dist/ollama.d.cts.map +1 -1
  109. package/dist/ollama.d.ts.map +1 -1
  110. package/dist/ollama.js +7 -7
  111. package/dist/ollama.js.map +1 -1
  112. package/dist/recorder.cjs +69 -21
  113. package/dist/recorder.cjs.map +1 -1
  114. package/dist/recorder.d.cts +50 -5
  115. package/dist/recorder.d.cts.map +1 -1
  116. package/dist/recorder.d.ts +50 -5
  117. package/dist/recorder.d.ts.map +1 -1
  118. package/dist/recorder.js +69 -21
  119. package/dist/recorder.js.map +1 -1
  120. package/dist/responses.cjs +3 -3
  121. package/dist/responses.cjs.map +1 -1
  122. package/dist/responses.d.cts.map +1 -1
  123. package/dist/responses.d.ts.map +1 -1
  124. package/dist/responses.js +4 -4
  125. package/dist/responses.js.map +1 -1
  126. package/dist/router.cjs +3 -1
  127. package/dist/router.cjs.map +1 -1
  128. package/dist/router.d.cts.map +1 -1
  129. package/dist/router.d.ts.map +1 -1
  130. package/dist/router.js +4 -2
  131. package/dist/router.js.map +1 -1
  132. package/dist/server.cjs +130 -54
  133. package/dist/server.cjs.map +1 -1
  134. package/dist/server.d.cts.map +1 -1
  135. package/dist/server.d.ts.map +1 -1
  136. package/dist/server.js +132 -56
  137. package/dist/server.js.map +1 -1
  138. package/dist/speech.cjs +3 -3
  139. package/dist/speech.cjs.map +1 -1
  140. package/dist/speech.d.cts.map +1 -1
  141. package/dist/speech.d.ts.map +1 -1
  142. package/dist/speech.js +4 -4
  143. package/dist/speech.js.map +1 -1
  144. package/dist/transcription.cjs +3 -3
  145. package/dist/transcription.cjs.map +1 -1
  146. package/dist/transcription.d.cts.map +1 -1
  147. package/dist/transcription.d.ts.map +1 -1
  148. package/dist/transcription.js +4 -4
  149. package/dist/transcription.js.map +1 -1
  150. package/dist/types.d.cts +32 -7
  151. package/dist/types.d.cts.map +1 -1
  152. package/dist/types.d.ts +32 -7
  153. package/dist/types.d.ts.map +1 -1
  154. package/dist/vector-types.d.cts.map +1 -1
  155. package/dist/vector-types.d.ts.map +1 -1
  156. package/dist/video.cjs +10 -4
  157. package/dist/video.cjs.map +1 -1
  158. package/dist/video.d.cts +1 -1
  159. package/dist/video.d.cts.map +1 -1
  160. package/dist/video.d.ts +1 -1
  161. package/dist/video.d.ts.map +1 -1
  162. package/dist/video.js +11 -5
  163. package/dist/video.js.map +1 -1
  164. package/dist/ws-gemini-live.cjs +1 -1
  165. package/dist/ws-gemini-live.cjs.map +1 -1
  166. package/dist/ws-gemini-live.d.cts.map +1 -1
  167. package/dist/ws-gemini-live.d.ts.map +1 -1
  168. package/dist/ws-gemini-live.js +2 -2
  169. package/dist/ws-gemini-live.js.map +1 -1
  170. package/dist/ws-realtime.cjs +1 -1
  171. package/dist/ws-realtime.cjs.map +1 -1
  172. package/dist/ws-realtime.d.cts.map +1 -1
  173. package/dist/ws-realtime.d.ts.map +1 -1
  174. package/dist/ws-realtime.js +2 -2
  175. package/dist/ws-realtime.js.map +1 -1
  176. package/dist/ws-responses.cjs +1 -1
  177. package/dist/ws-responses.cjs.map +1 -1
  178. package/dist/ws-responses.js +2 -2
  179. package/dist/ws-responses.js.map +1 -1
  180. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ollama.cjs","names":["flattenHeaders","getTestId","matchFixture","applyChaos","proxyAndRecord","isErrorResponse","isContentWithToolCallsResponse","createInterruptionSignal","writeNDJSONStream","isTextResponse","isToolCallResponse"],"sources":["../src/ollama.ts"],"sourcesContent":["/**\n * Ollama API endpoint support.\n *\n * Translates incoming /api/chat and /api/generate requests into the\n * ChatCompletionRequest format used by the fixture router, and converts\n * fixture responses back into Ollama's NDJSON streaming or non-streaming format.\n *\n * Key differences from OpenAI:\n * - Ollama defaults to stream: true (opposite of OpenAI)\n * - Streaming uses NDJSON, not SSE\n * - Tool call arguments are objects, not JSON strings\n * - Tool calls have no id field\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n flattenHeaders,\n getTestId,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { writeNDJSONStream } from \"./ndjson-writer.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Ollama request types ────────────────────────────────────────────────────\n\ninterface OllamaMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string;\n}\n\ninterface OllamaToolDef {\n type: string;\n function: {\n name: string;\n description?: string;\n parameters?: object;\n };\n}\n\ninterface OllamaRequest {\n model: string;\n messages: OllamaMessage[];\n stream?: boolean; // default true!\n options?: { temperature?: number; num_predict?: number };\n tools?: OllamaToolDef[];\n}\n\ninterface OllamaGenerateRequest {\n model: string;\n prompt: string;\n stream?: boolean; // default true!\n options?: { temperature?: number; num_predict?: number };\n}\n\n// ─── Duration fields (zeroed, required on final/non-streaming responses) ────\n\nconst DURATION_FIELDS = {\n done_reason: \"stop\" as const,\n total_duration: 0,\n load_duration: 0,\n prompt_eval_count: 0,\n prompt_eval_duration: 0,\n eval_count: 0,\n eval_duration: 0,\n};\n\n// ─── Input conversion: Ollama → ChatCompletionRequest ────────────────────────\n\nexport function ollamaToCompletionRequest(req: OllamaRequest): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n for (const msg of req.messages) {\n messages.push({\n role: msg.role as ChatMessage[\"role\"],\n content: msg.content,\n });\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n tools = req.tools.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n return {\n model: req.model,\n messages,\n stream: req.stream ?? true,\n temperature: req.options?.temperature,\n max_tokens: req.options?.num_predict,\n tools,\n };\n}\n\nfunction ollamaGenerateToCompletionRequest(req: OllamaGenerateRequest): ChatCompletionRequest {\n return {\n model: req.model,\n messages: [{ role: \"user\", content: req.prompt }],\n stream: req.stream ?? true,\n temperature: req.options?.temperature,\n max_tokens: req.options?.num_predict,\n };\n}\n\n// ─── Response builders: /api/chat ────────────────────────────────────────────\n\nfunction buildOllamaChatTextChunks(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n): object[] {\n const chunks: object[] = [];\n\n // Reasoning chunks (before content)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\n\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n message: { role: \"assistant\", content: slice },\n done: false,\n });\n }\n\n // Final chunk with done: true and all duration fields\n chunks.push({\n model,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatTextResponse(content: string, model: string, reasoning?: string): object {\n return {\n model,\n message: {\n role: \"assistant\",\n content,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\nfunction buildOllamaChatToolCallChunks(\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n): object[] {\n const ollamaToolCalls = toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n // Tool calls are sent in a single chunk (no streaming of individual args)\n const chunks: object[] = [];\n chunks.push({\n model,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: ollamaToolCalls,\n },\n done: false,\n });\n\n // Final chunk\n chunks.push({\n model,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatToolCallResponse(\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n): object {\n const ollamaToolCalls = toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n return {\n model,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: ollamaToolCalls,\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\n// ─── Response builders: /api/chat — content + tool calls ────────────────────\n\nfunction buildOllamaChatContentWithToolCallsChunks(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n logger: Logger,\n): object[] {\n const chunks: object[] = [];\n\n // Content chunks first\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n message: { role: \"assistant\", content: slice },\n done: false,\n });\n }\n\n // Tool calls in a single chunk (same as tool-call-only path)\n const ollamaToolCalls = toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n chunks.push({\n model,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: ollamaToolCalls,\n },\n done: false,\n });\n\n // Final chunk\n chunks.push({\n model,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n): object {\n const ollamaToolCalls = toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n return {\n model,\n message: {\n role: \"assistant\",\n content,\n tool_calls: ollamaToolCalls,\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\n// ─── Response builders: /api/generate ────────────────────────────────────────\n\nfunction buildOllamaGenerateTextChunks(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n): object[] {\n const chunks: object[] = [];\n const createdAt = new Date().toISOString();\n\n // Reasoning chunks (before content)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n response: \"\",\n reasoning_content: slice,\n done: false,\n });\n }\n }\n\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n response: slice,\n done: false,\n });\n }\n\n // Final chunk\n chunks.push({\n model,\n created_at: createdAt,\n response: \"\",\n done: true,\n ...DURATION_FIELDS,\n context: [],\n });\n\n return chunks;\n}\n\nfunction buildOllamaGenerateTextResponse(\n content: string,\n model: string,\n reasoning?: string,\n): object {\n return {\n model,\n created_at: new Date().toISOString(),\n response: content,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n done: true,\n ...DURATION_FIELDS,\n context: [],\n };\n}\n\n// ─── Request handler: /api/chat ──────────────────────────────────────────────\n\nexport async function handleOllama(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n const urlPath = req.url ?? \"/api/chat\";\n\n let ollamaReq: OllamaRequest;\n try {\n ollamaReq = JSON.parse(raw) as OllamaRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!ollamaReq.messages || !Array.isArray(ollamaReq.messages)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Invalid request: messages array is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = ollamaToCompletionRequest(ollamaReq);\n completionReq._endpointType = \"chat\";\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n logger.debug(`No fixture matched for request`);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const proxied = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"ollama\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${urlPath}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const response = fixture.response;\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Ollama defaults to streaming when stream is absent or true\n const streaming = ollamaReq.stream !== false;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, JSON.stringify(response));\n return;\n }\n\n // Content + tool calls response (must be checked before text/tool-only branches)\n if (isContentWithToolCallsResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildOllamaChatContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n completionReq.model,\n logger,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatContentWithToolCallsChunks(\n response.content,\n response.toolCalls,\n completionReq.model,\n chunkSize,\n logger,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildOllamaChatTextResponse(\n response.content,\n completionReq.model,\n response.reasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatTextChunks(\n response.content,\n completionReq.model,\n chunkSize,\n response.reasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildOllamaChatToolCallResponse(response.toolCalls, completionReq.model, logger);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatToolCallChunks(response.toolCalls, completionReq.model, logger);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n type: \"server_error\",\n },\n }),\n );\n}\n\n// ─── Request handler: /api/generate ──────────────────────────────────────────\n\nexport async function handleOllamaGenerate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n setCorsHeaders(res);\n\n const urlPath = req.url ?? \"/api/generate\";\n\n let generateReq: OllamaGenerateRequest;\n try {\n generateReq = JSON.parse(raw) as OllamaGenerateRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!generateReq.prompt || typeof generateReq.prompt !== \"string\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Invalid request: prompt field is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = ollamaGenerateToCompletionRequest(generateReq);\n completionReq._endpointType = \"chat\";\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const proxied = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"ollama\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n defaults.logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${urlPath}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const response = fixture.response;\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Ollama defaults to streaming when stream is absent or true\n const streaming = generateReq.stream !== false;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, JSON.stringify(response));\n return;\n }\n\n // Text response (only type supported for /api/generate)\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildOllamaGenerateTextResponse(\n response.content,\n completionReq.model,\n response.reasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaGenerateTextChunks(\n response.content,\n completionReq.model,\n chunkSize,\n response.reasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call fixtures matched but not supported on /api/generate\n if (isToolCallResponse(response) || isContentWithToolCallsResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 400, fixture },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Tool call fixtures are not supported on /api/generate — use /api/chat instead\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n type: \"server_error\",\n },\n }),\n );\n}\n"],"mappings":";;;;;;;;;AAyEA,MAAM,kBAAkB;CACtB,aAAa;CACb,gBAAgB;CAChB,eAAe;CACf,mBAAmB;CACnB,sBAAsB;CACtB,YAAY;CACZ,eAAe;CAChB;AAID,SAAgB,0BAA0B,KAA2C;CACnF,MAAM,WAA0B,EAAE;AAElC,MAAK,MAAM,OAAO,IAAI,SACpB,UAAS,KAAK;EACZ,MAAM,IAAI;EACV,SAAS,IAAI;EACd,CAAC;CAIJ,IAAI;AACJ,KAAI,IAAI,SAAS,IAAI,MAAM,SAAS,EAClC,SAAQ,IAAI,MAAM,KAAK,OAAO;EAC5B,MAAM;EACN,UAAU;GACR,MAAM,EAAE,SAAS;GACjB,aAAa,EAAE,SAAS;GACxB,YAAY,EAAE,SAAS;GACxB;EACF,EAAE;AAGL,QAAO;EACL,OAAO,IAAI;EACX;EACA,QAAQ,IAAI,UAAU;EACtB,aAAa,IAAI,SAAS;EAC1B,YAAY,IAAI,SAAS;EACzB;EACD;;AAGH,SAAS,kCAAkC,KAAmD;AAC5F,QAAO;EACL,OAAO,IAAI;EACX,UAAU,CAAC;GAAE,MAAM;GAAQ,SAAS,IAAI;GAAQ,CAAC;EACjD,QAAQ,IAAI,UAAU;EACtB,aAAa,IAAI,SAAS;EAC1B,YAAY,IAAI,SAAS;EAC1B;;AAKH,SAAS,0BACP,SACA,OACA,WACA,WACU;CACV,MAAM,SAAmB,EAAE;AAG3B,KAAI,UACF,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK,WAAW;EACpD,MAAM,QAAQ,UAAU,MAAM,GAAG,IAAI,UAAU;AAC/C,SAAO,KAAK;GACV;GACA,SAAS;IAAE,MAAM;IAAa,SAAS;IAAI,mBAAmB;IAAO;GACrE,MAAM;GACP,CAAC;;AAIN,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,WAAW;EAClD,MAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,UAAU;AAC7C,SAAO,KAAK;GACV;GACA,SAAS;IAAE,MAAM;IAAa,SAAS;IAAO;GAC9C,MAAM;GACP,CAAC;;AAIJ,QAAO,KAAK;EACV;EACA,SAAS;GAAE,MAAM;GAAa,SAAS;GAAI;EAC3C,MAAM;EACN,GAAG;EACJ,CAAC;AAEF,QAAO;;AAGT,SAAS,4BAA4B,SAAiB,OAAe,WAA4B;AAC/F,QAAO;EACL;EACA,SAAS;GACP,MAAM;GACN;GACA,GAAI,YAAY,EAAE,mBAAmB,WAAW,GAAG,EAAE;GACtD;EACD,MAAM;EACN,GAAG;EACJ;;AAGH,SAAS,8BACP,WACA,OACA,QACU;CACV,MAAM,kBAAkB,UAAU,KAAK,OAAO;EAC5C,IAAI;AACJ,MAAI;AACF,aAAU,KAAK,MAAM,GAAG,aAAa,KAAK;UACpC;AACN,UAAO,KACL,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,aAAU,EAAE;;AAEd,SAAO,EACL,UAAU;GACR,MAAM,GAAG;GACT,WAAW;GACZ,EACF;GACD;CAGF,MAAM,SAAmB,EAAE;AAC3B,QAAO,KAAK;EACV;EACA,SAAS;GACP,MAAM;GACN,SAAS;GACT,YAAY;GACb;EACD,MAAM;EACP,CAAC;AAGF,QAAO,KAAK;EACV;EACA,SAAS;GAAE,MAAM;GAAa,SAAS;GAAI;EAC3C,MAAM;EACN,GAAG;EACJ,CAAC;AAEF,QAAO;;AAGT,SAAS,gCACP,WACA,OACA,QACQ;AAmBR,QAAO;EACL;EACA,SAAS;GACP,MAAM;GACN,SAAS;GACT,YAvBoB,UAAU,KAAK,OAAO;IAC5C,IAAI;AACJ,QAAI;AACF,eAAU,KAAK,MAAM,GAAG,aAAa,KAAK;YACpC;AACN,YAAO,KACL,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,eAAU,EAAE;;AAEd,WAAO,EACL,UAAU;KACR,MAAM,GAAG;KACT,WAAW;KACZ,EACF;KACD;GAQC;EACD,MAAM;EACN,GAAG;EACJ;;AAKH,SAAS,0CACP,SACA,WACA,OACA,WACA,QACU;CACV,MAAM,SAAmB,EAAE;AAG3B,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,WAAW;EAClD,MAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,UAAU;AAC7C,SAAO,KAAK;GACV;GACA,SAAS;IAAE,MAAM;IAAa,SAAS;IAAO;GAC9C,MAAM;GACP,CAAC;;CAIJ,MAAM,kBAAkB,UAAU,KAAK,OAAO;EAC5C,IAAI;AACJ,MAAI;AACF,aAAU,KAAK,MAAM,GAAG,aAAa,KAAK;UACpC;AACN,UAAO,KACL,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,aAAU,EAAE;;AAEd,SAAO,EACL,UAAU;GACR,MAAM,GAAG;GACT,WAAW;GACZ,EACF;GACD;AAEF,QAAO,KAAK;EACV;EACA,SAAS;GACP,MAAM;GACN,SAAS;GACT,YAAY;GACb;EACD,MAAM;EACP,CAAC;AAGF,QAAO,KAAK;EACV;EACA,SAAS;GAAE,MAAM;GAAa,SAAS;GAAI;EAC3C,MAAM;EACN,GAAG;EACJ,CAAC;AAEF,QAAO;;AAGT,SAAS,4CACP,SACA,WACA,OACA,QACQ;AAmBR,QAAO;EACL;EACA,SAAS;GACP,MAAM;GACN;GACA,YAvBoB,UAAU,KAAK,OAAO;IAC5C,IAAI;AACJ,QAAI;AACF,eAAU,KAAK,MAAM,GAAG,aAAa,KAAK;YACpC;AACN,YAAO,KACL,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,eAAU,EAAE;;AAEd,WAAO,EACL,UAAU;KACR,MAAM,GAAG;KACT,WAAW;KACZ,EACF;KACD;GAQC;EACD,MAAM;EACN,GAAG;EACJ;;AAKH,SAAS,8BACP,SACA,OACA,WACA,WACU;CACV,MAAM,SAAmB,EAAE;CAC3B,MAAM,6BAAY,IAAI,MAAM,EAAC,aAAa;AAG1C,KAAI,UACF,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK,WAAW;EACpD,MAAM,QAAQ,UAAU,MAAM,GAAG,IAAI,UAAU;AAC/C,SAAO,KAAK;GACV;GACA,YAAY;GACZ,UAAU;GACV,mBAAmB;GACnB,MAAM;GACP,CAAC;;AAIN,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,WAAW;EAClD,MAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,UAAU;AAC7C,SAAO,KAAK;GACV;GACA,YAAY;GACZ,UAAU;GACV,MAAM;GACP,CAAC;;AAIJ,QAAO,KAAK;EACV;EACA,YAAY;EACZ,UAAU;EACV,MAAM;EACN,GAAG;EACH,SAAS,EAAE;EACZ,CAAC;AAEF,QAAO;;AAGT,SAAS,gCACP,SACA,OACA,WACQ;AACR,QAAO;EACL;EACA,6BAAY,IAAI,MAAM,EAAC,aAAa;EACpC,UAAU;EACV,GAAI,YAAY,EAAE,mBAAmB,WAAW,GAAG,EAAE;EACrD,MAAM;EACN,GAAG;EACH,SAAS,EAAE;EACZ;;AAKH,eAAsB,aACpB,KACA,KACA,KACA,UACA,SACA,UACA,gBACe;CACf,MAAM,EAAE,WAAW;AACnB,gBAAe,IAAI;CAEnB,MAAM,UAAU,IAAI,OAAO;CAE3B,IAAI;AACJ,KAAI;AACF,cAAY,KAAK,MAAM,IAAI;SACrB;AACN,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAGF,KAAI,CAAC,UAAU,YAAY,CAAC,MAAM,QAAQ,UAAU,SAAS,EAAE;AAC7D,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAIF,MAAM,gBAAgB,0BAA0B,UAAU;AAC1D,eAAc,gBAAgB;CAE9B,MAAM,SAASC,0BAAU,IAAI;CAC7B,MAAM,UAAUC,4BACd,UACA,eACA,QAAQ,6BAA6B,OAAO,EAC5C,SAAS,iBACV;AAED,KAAI,SAAS;AACX,UAAQ,2BAA2B,SAAS,UAAU,OAAO;AAC7D,SAAO,MAAM,oBAAoB,KAAK,UAAU,QAAQ,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG;OAE/E,QAAO,MAAM,iCAAiC;AAGhD,KACEC,yBACE,KACA,SACA,SAAS,OACT,IAAI,SACJ,SACA;EACE,QAAQ,IAAI,UAAU;EACtB,MAAM;EACN,SAASH,+BAAe,IAAI,QAAQ;EACpC,MAAM;EACP,EACD,SAAS,UACT,SAAS,OACV,CAED;AAEF,KAAI,CAAC,SAAS;AACZ,MAAI,SAAS,QAWX;OAVgB,MAAMI,gCACpB,KACA,KACA,eACA,UACA,SACA,UACA,UACA,IACD,EACY;AACX,YAAQ,IAAI;KACV,QAAQ,IAAI,UAAU;KACtB,MAAM;KACN,SAASJ,+BAAe,IAAI,QAAQ;KACpC,MAAM;KACN,UAAU;MAAE,QAAQ,IAAI,cAAc;MAAK,SAAS;MAAM,QAAQ;MAAS;KAC5E,CAAC;AACF;;;EAGJ,MAAM,eAAe,SAAS,SAAS,MAAM;EAC7C,MAAM,gBAAgB,SAAS,SAC3B,oCACA;AACJ,MAAI,SAAS,OACX,QAAO,MAAM,kCAAkC,IAAI,UAAU,OAAO,GAAG,UAAU;AAEnF,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAc,SAAS;IAAM;GAClD,CAAC;AACF,wCACE,KACA,cACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAGF,MAAM,WAAW,QAAQ;CACzB,MAAM,UAAU,QAAQ,WAAW,SAAS;CAC5C,MAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,aAAa,SAAS,UAAU;CAGtE,MAAM,YAAY,UAAU,WAAW;AAGvC,KAAIK,gCAAgB,SAAS,EAAE;EAC7B,MAAM,SAAS,SAAS,UAAU;AAClC,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAASL,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE;IAAQ;IAAS;GAC9B,CAAC;AACF,wCAAmB,KAAK,QAAQ,KAAK,UAAU,SAAS,CAAC;AACzD;;AAIF,KAAIM,+CAA+B,SAAS,EAAE;EAC5C,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAASN,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,MAAI,CAAC,WAAW;GACd,MAAM,OAAO,4CACX,SAAS,SACT,SAAS,WACT,cAAc,OACd,OACD;AACD,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,KAAK,CAAC;SACxB;GACL,MAAM,SAAS,0CACb,SAAS,SACT,SAAS,WACT,cAAc,OACd,WACA,OACD;GACD,MAAM,eAAeO,8CAAyB,QAAQ;AAOtD,OAAI,CANc,MAAMC,wCAAkB,KAAK,QAAQ;IACrD;IACA,kBAAkB,QAAQ;IAC1B,QAAQ,cAAc;IACtB,aAAa,cAAc;IAC5B,CAAC,EACc;AACd,QAAI,CAAC,IAAI,cAAe,KAAI,SAAS;AACrC,iBAAa,SAAS,cAAc;AACpC,iBAAa,SAAS,kBAAkB,cAAc,QAAQ;;AAEhE,iBAAc,SAAS;;AAEzB;;AAIF,KAAIC,+BAAe,SAAS,EAAE;EAC5B,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAAST,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,MAAI,CAAC,WAAW;GACd,MAAM,OAAO,4BACX,SAAS,SACT,cAAc,OACd,SAAS,UACV;AACD,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,KAAK,CAAC;SACxB;GACL,MAAM,SAAS,0BACb,SAAS,SACT,cAAc,OACd,WACA,SAAS,UACV;GACD,MAAM,eAAeO,8CAAyB,QAAQ;AAOtD,OAAI,CANc,MAAMC,wCAAkB,KAAK,QAAQ;IACrD;IACA,kBAAkB,QAAQ;IAC1B,QAAQ,cAAc;IACtB,aAAa,cAAc;IAC5B,CAAC,EACc;AACd,QAAI,CAAC,IAAI,cAAe,KAAI,SAAS;AACrC,iBAAa,SAAS,cAAc;AACpC,iBAAa,SAAS,kBAAkB,cAAc,QAAQ;;AAEhE,iBAAc,SAAS;;AAEzB;;AAIF,KAAIE,mCAAmB,SAAS,EAAE;EAChC,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAASV,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,MAAI,CAAC,WAAW;GACd,MAAM,OAAO,gCAAgC,SAAS,WAAW,cAAc,OAAO,OAAO;AAC7F,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,KAAK,CAAC;SACxB;GACL,MAAM,SAAS,8BAA8B,SAAS,WAAW,cAAc,OAAO,OAAO;GAC7F,MAAM,eAAeO,8CAAyB,QAAQ;AAOtD,OAAI,CANc,MAAMC,wCAAkB,KAAK,QAAQ;IACrD;IACA,kBAAkB,QAAQ;IAC1B,QAAQ,cAAc;IACtB,aAAa,cAAc;IAC5B,CAAC,EACc;AACd,QAAI,CAAC,IAAI,cAAe,KAAI,SAAS;AACrC,iBAAa,SAAS,cAAc;AACpC,iBAAa,SAAS,kBAAkB,cAAc,QAAQ;;AAEhE,iBAAc,SAAS;;AAEzB;;AAIF,SAAQ,IAAI;EACV,QAAQ,IAAI,UAAU;EACtB,MAAM;EACN,SAASR,+BAAe,IAAI,QAAQ;EACpC,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK;GAAS;EACnC,CAAC;AACF,uCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;EACL,SAAS;EACT,MAAM;EACP,EACF,CAAC,CACH;;AAKH,eAAsB,qBACpB,KACA,KACA,KACA,UACA,SACA,UACA,gBACe;AACf,gBAAe,IAAI;CAEnB,MAAM,UAAU,IAAI,OAAO;CAE3B,IAAI;AACJ,KAAI;AACF,gBAAc,KAAK,MAAM,IAAI;SACvB;AACN,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAGF,KAAI,CAAC,YAAY,UAAU,OAAO,YAAY,WAAW,UAAU;AACjE,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAIF,MAAM,gBAAgB,kCAAkC,YAAY;AACpE,eAAc,gBAAgB;CAE9B,MAAM,SAASC,0BAAU,IAAI;CAC7B,MAAM,UAAUC,4BACd,UACA,eACA,QAAQ,6BAA6B,OAAO,EAC5C,SAAS,iBACV;AAED,KAAI,SAAS;AACX,UAAQ,2BAA2B,SAAS,UAAU,OAAO;AAC7D,WAAS,OAAO,MAAM,oBAAoB,KAAK,UAAU,QAAQ,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG;OAExF,UAAS,OAAO,MAAM,iCAAiC;AAGzD,KACEC,yBACE,KACA,SACA,SAAS,OACT,IAAI,SACJ,SACA;EACE,QAAQ,IAAI,UAAU;EACtB,MAAM;EACN,SAASH,+BAAe,IAAI,QAAQ;EACpC,MAAM;EACP,EACD,SAAS,UACT,SAAS,OACV,CAED;AAEF,KAAI,CAAC,SAAS;AACZ,MAAI,SAAS,QAWX;OAVgB,MAAMI,gCACpB,KACA,KACA,eACA,UACA,SACA,UACA,UACA,IACD,EACY;AACX,YAAQ,IAAI;KACV,QAAQ,IAAI,UAAU;KACtB,MAAM;KACN,SAASJ,+BAAe,IAAI,QAAQ;KACpC,MAAM;KACN,UAAU;MAAE,QAAQ,IAAI,cAAc;MAAK,SAAS;MAAM,QAAQ;MAAS;KAC5E,CAAC;AACF;;;EAGJ,MAAM,eAAe,SAAS,SAAS,MAAM;EAC7C,MAAM,gBAAgB,SAAS,SAC3B,oCACA;AACJ,MAAI,SAAS,OACX,UAAS,OAAO,MAAM,kCAAkC,IAAI,UAAU,OAAO,GAAG,UAAU;AAE5F,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAc,SAAS;IAAM;GAClD,CAAC;AACF,wCACE,KACA,cACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAGF,MAAM,WAAW,QAAQ;CACzB,MAAM,UAAU,QAAQ,WAAW,SAAS;CAC5C,MAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,aAAa,SAAS,UAAU;CAGtE,MAAM,YAAY,YAAY,WAAW;AAGzC,KAAIK,gCAAgB,SAAS,EAAE;EAC7B,MAAM,SAAS,SAAS,UAAU;AAClC,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAASL,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE;IAAQ;IAAS;GAC9B,CAAC;AACF,wCAAmB,KAAK,QAAQ,KAAK,UAAU,SAAS,CAAC;AACzD;;AAIF,KAAIS,+BAAe,SAAS,EAAE;EAC5B,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAAST,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,MAAI,CAAC,WAAW;GACd,MAAM,OAAO,gCACX,SAAS,SACT,cAAc,OACd,SAAS,UACV;AACD,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,KAAK,CAAC;SACxB;GACL,MAAM,SAAS,8BACb,SAAS,SACT,cAAc,OACd,WACA,SAAS,UACV;GACD,MAAM,eAAeO,8CAAyB,QAAQ;AAOtD,OAAI,CANc,MAAMC,wCAAkB,KAAK,QAAQ;IACrD;IACA,kBAAkB,QAAQ;IAC1B,QAAQ,cAAc;IACtB,aAAa,cAAc;IAC5B,CAAC,EACc;AACd,QAAI,CAAC,IAAI,cAAe,KAAI,SAAS;AACrC,iBAAa,SAAS,cAAc;AACpC,iBAAa,SAAS,kBAAkB,cAAc,QAAQ;;AAEhE,iBAAc,SAAS;;AAEzB;;AAIF,KAAIE,mCAAmB,SAAS,IAAIJ,+CAA+B,SAAS,EAAE;AAC5E,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM;GACN,SAASN,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAIF,SAAQ,IAAI;EACV,QAAQ,IAAI,UAAU;EACtB,MAAM;EACN,SAASA,+BAAe,IAAI,QAAQ;EACpC,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK;GAAS;EACnC,CAAC;AACF,uCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;EACL,SAAS;EACT,MAAM;EACP,EACF,CAAC,CACH"}
1
+ {"version":3,"file":"ollama.cjs","names":["flattenHeaders","getTestId","matchFixture","applyChaos","proxyAndRecord","resolveResponse","isErrorResponse","isContentWithToolCallsResponse","createInterruptionSignal","writeNDJSONStream","isTextResponse","isToolCallResponse"],"sources":["../src/ollama.ts"],"sourcesContent":["/**\n * Ollama API endpoint support.\n *\n * Translates incoming /api/chat and /api/generate requests into the\n * ChatCompletionRequest format used by the fixture router, and converts\n * fixture responses back into Ollama's NDJSON streaming or non-streaming format.\n *\n * Key differences from OpenAI:\n * - Ollama defaults to stream: true (opposite of OpenAI)\n * - Streaming uses NDJSON, not SSE\n * - Tool call arguments are objects, not JSON strings\n * - Tool calls have no id field\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { writeNDJSONStream } from \"./ndjson-writer.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Ollama request types ────────────────────────────────────────────────────\n\ninterface OllamaMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string;\n}\n\ninterface OllamaToolDef {\n type: string;\n function: {\n name: string;\n description?: string;\n parameters?: object;\n };\n}\n\ninterface OllamaRequest {\n model: string;\n messages: OllamaMessage[];\n stream?: boolean; // default true!\n options?: { temperature?: number; num_predict?: number };\n tools?: OllamaToolDef[];\n}\n\ninterface OllamaGenerateRequest {\n model: string;\n prompt: string;\n stream?: boolean; // default true!\n options?: { temperature?: number; num_predict?: number };\n}\n\n// ─── Duration fields (zeroed, required on final/non-streaming responses) ────\n\nconst DURATION_FIELDS = {\n done_reason: \"stop\" as const,\n total_duration: 0,\n load_duration: 0,\n prompt_eval_count: 0,\n prompt_eval_duration: 0,\n eval_count: 0,\n eval_duration: 0,\n};\n\n// ─── Input conversion: Ollama → ChatCompletionRequest ────────────────────────\n\nexport function ollamaToCompletionRequest(req: OllamaRequest): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n for (const msg of req.messages) {\n messages.push({\n role: msg.role as ChatMessage[\"role\"],\n content: msg.content,\n });\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n tools = req.tools.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n return {\n model: req.model,\n messages,\n stream: req.stream ?? true,\n temperature: req.options?.temperature,\n max_tokens: req.options?.num_predict,\n tools,\n };\n}\n\nfunction ollamaGenerateToCompletionRequest(req: OllamaGenerateRequest): ChatCompletionRequest {\n return {\n model: req.model,\n messages: [{ role: \"user\", content: req.prompt }],\n stream: req.stream ?? true,\n temperature: req.options?.temperature,\n max_tokens: req.options?.num_predict,\n };\n}\n\n// ─── Response builders: /api/chat ────────────────────────────────────────────\n\nfunction buildOllamaChatTextChunks(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n): object[] {\n const chunks: object[] = [];\n\n // Reasoning chunks (before content)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\n\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n message: { role: \"assistant\", content: slice },\n done: false,\n });\n }\n\n // Final chunk with done: true and all duration fields\n chunks.push({\n model,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatTextResponse(content: string, model: string, reasoning?: string): object {\n return {\n model,\n message: {\n role: \"assistant\",\n content,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\nfunction buildOllamaChatToolCallChunks(\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n): object[] {\n const ollamaToolCalls = toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n // Tool calls are sent in a single chunk (no streaming of individual args)\n const chunks: object[] = [];\n chunks.push({\n model,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: ollamaToolCalls,\n },\n done: false,\n });\n\n // Final chunk\n chunks.push({\n model,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatToolCallResponse(\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n): object {\n const ollamaToolCalls = toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n return {\n model,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: ollamaToolCalls,\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\n// ─── Response builders: /api/chat — content + tool calls ────────────────────\n\nfunction buildOllamaChatContentWithToolCallsChunks(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n logger: Logger,\n): object[] {\n const chunks: object[] = [];\n\n // Content chunks first\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n message: { role: \"assistant\", content: slice },\n done: false,\n });\n }\n\n // Tool calls in a single chunk (same as tool-call-only path)\n const ollamaToolCalls = toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n chunks.push({\n model,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: ollamaToolCalls,\n },\n done: false,\n });\n\n // Final chunk\n chunks.push({\n model,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n): object {\n const ollamaToolCalls = toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n return {\n model,\n message: {\n role: \"assistant\",\n content,\n tool_calls: ollamaToolCalls,\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\n// ─── Response builders: /api/generate ────────────────────────────────────────\n\nfunction buildOllamaGenerateTextChunks(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n): object[] {\n const chunks: object[] = [];\n const createdAt = new Date().toISOString();\n\n // Reasoning chunks (before content)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n response: \"\",\n reasoning_content: slice,\n done: false,\n });\n }\n }\n\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n response: slice,\n done: false,\n });\n }\n\n // Final chunk\n chunks.push({\n model,\n created_at: createdAt,\n response: \"\",\n done: true,\n ...DURATION_FIELDS,\n context: [],\n });\n\n return chunks;\n}\n\nfunction buildOllamaGenerateTextResponse(\n content: string,\n model: string,\n reasoning?: string,\n): object {\n return {\n model,\n created_at: new Date().toISOString(),\n response: content,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n done: true,\n ...DURATION_FIELDS,\n context: [],\n };\n}\n\n// ─── Request handler: /api/chat ──────────────────────────────────────────────\n\nexport async function handleOllama(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n const urlPath = req.url ?? \"/api/chat\";\n\n let ollamaReq: OllamaRequest;\n try {\n ollamaReq = JSON.parse(raw) as OllamaRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!ollamaReq.messages || !Array.isArray(ollamaReq.messages)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Invalid request: messages array is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = ollamaToCompletionRequest(ollamaReq);\n completionReq._endpointType = \"chat\";\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n logger.debug(`No fixture matched for request`);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"ollama\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${urlPath}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Ollama defaults to streaming when stream is absent or true\n const streaming = ollamaReq.stream !== false;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, JSON.stringify(response));\n return;\n }\n\n // Content + tool calls response (must be checked before text/tool-only branches)\n if (isContentWithToolCallsResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildOllamaChatContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n completionReq.model,\n logger,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatContentWithToolCallsChunks(\n response.content,\n response.toolCalls,\n completionReq.model,\n chunkSize,\n logger,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildOllamaChatTextResponse(\n response.content,\n completionReq.model,\n response.reasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatTextChunks(\n response.content,\n completionReq.model,\n chunkSize,\n response.reasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildOllamaChatToolCallResponse(response.toolCalls, completionReq.model, logger);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatToolCallChunks(response.toolCalls, completionReq.model, logger);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n type: \"server_error\",\n },\n }),\n );\n}\n\n// ─── Request handler: /api/generate ──────────────────────────────────────────\n\nexport async function handleOllamaGenerate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n setCorsHeaders(res);\n\n const urlPath = req.url ?? \"/api/generate\";\n\n let generateReq: OllamaGenerateRequest;\n try {\n generateReq = JSON.parse(raw) as OllamaGenerateRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!generateReq.prompt || typeof generateReq.prompt !== \"string\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Invalid request: prompt field is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = ollamaGenerateToCompletionRequest(generateReq);\n completionReq._endpointType = \"chat\";\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"ollama\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n defaults.logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${urlPath}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Ollama defaults to streaming when stream is absent or true\n const streaming = generateReq.stream !== false;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, JSON.stringify(response));\n return;\n }\n\n // Text response (only type supported for /api/generate)\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildOllamaGenerateTextResponse(\n response.content,\n completionReq.model,\n response.reasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaGenerateTextChunks(\n response.content,\n completionReq.model,\n chunkSize,\n response.reasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call fixtures matched but not supported on /api/generate\n if (isToolCallResponse(response) || isContentWithToolCallsResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 400, fixture },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Tool call fixtures are not supported on /api/generate — use /api/chat instead\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n type: \"server_error\",\n },\n }),\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ollama.d.cts","names":[],"sources":["../src/ollama.ts"],"sourcesContent":[],"mappings":";;;;;;UA0CU,aAAA,CA2C0E;EAsV9D,IAAA,EAAA,QAAA,GAAY,MAAA,GAAA,WAAA,GAAA,MAAA;EAAA,OAAA,EAAA,MAAA;;UA5XxB,aAAA,CA8XE;MAEA,EAAA,MAAA;UACD,EAAA;IACC,IAAA,EAAA,MAAA;IACY,WAAK,CAAA,EAAA,MAAA;IAC1B,UAAA,CAAA,EAAA,MAAA;EAAO,CAAA;AA0SV;UArqBU,aAAA,CAqqBgC;OACnC,EAAA,MAAK;UACL,EArqBK,aAqqBA,EAAA;QAEA,CAAA,EAAA,OAAA;SACD,CAAA,EAAA;IACC,WAAA,CAAA,EAAA,MAAA;IACY,WAAK,CAAA,EAAA,MAAA;;EACnB,KAAA,CAAA,EAxqBA,aAwqBA,EAAA;;iBAhpBM,yBAAA,MAA+B,gBAAgB;iBAsVzC,YAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B;iBA0SmB,oBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B"}
1
+ {"version":3,"file":"ollama.d.cts","names":[],"sources":["../src/ollama.ts"],"sourcesContent":[],"mappings":";;;;;;UA2CU,aAAA,CA2C0E;EAsV9D,IAAA,EAAA,QAAA,GAAY,MAAA,GAAA,WAAA,GAAA,MAAA;EAAA,OAAA,EAAA,MAAA;;UA5XxB,aAAA,CA8XE;MAEA,EAAA,MAAA;UACD,EAAA;IACC,IAAA,EAAA,MAAA;IACY,WAAK,CAAA,EAAA,MAAA;IAC1B,UAAA,CAAA,EAAA,MAAA;EAAO,CAAA;AA2SV;UAtqBU,aAAA,CAsqBgC;OACnC,EAAA,MAAK;UACL,EAtqBK,aAsqBA,EAAA;QAEA,CAAA,EAAA,OAAA;SACD,CAAA,EAAA;IACC,WAAA,CAAA,EAAA,MAAA;IACY,WAAK,CAAA,EAAA,MAAA;;EACnB,KAAA,CAAA,EAzqBA,aAyqBA,EAAA;;iBAjpBM,yBAAA,MAA+B,gBAAgB;iBAsVzC,YAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B;iBA2SmB,oBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B"}
@@ -1 +1 @@
1
- {"version":3,"file":"ollama.d.ts","names":[],"sources":["../src/ollama.ts"],"sourcesContent":[],"mappings":";;;;;;UA0CU,aAAA,CA2C0E;EAsV9D,IAAA,EAAA,QAAA,GAAY,MAAA,GAAA,WAAA,GAAA,MAAA;EAAA,OAAA,EAAA,MAAA;;UA5XxB,aAAA,CA8XE;MAEA,EAAA,MAAA;UACD,EAAA;IACC,IAAA,EAAA,MAAA;IACY,WAAK,CAAA,EAAA,MAAA;IAC1B,UAAA,CAAA,EAAA,MAAA;EAAO,CAAA;AA0SV;UArqBU,aAAA,CAqqBgC;OACnC,EAAA,MAAK;UACL,EArqBK,aAqqBA,EAAA;QAEA,CAAA,EAAA,OAAA;SACD,CAAA,EAAA;IACC,WAAA,CAAA,EAAA,MAAA;IACY,WAAK,CAAA,EAAA,MAAA;;EACnB,KAAA,CAAA,EAxqBA,aAwqBA,EAAA;;iBAhpBM,yBAAA,MAA+B,gBAAgB;iBAsVzC,YAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B;iBA0SmB,oBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B"}
1
+ {"version":3,"file":"ollama.d.ts","names":[],"sources":["../src/ollama.ts"],"sourcesContent":[],"mappings":";;;;;;UA2CU,aAAA,CA2C0E;EAsV9D,IAAA,EAAA,QAAA,GAAY,MAAA,GAAA,WAAA,GAAA,MAAA;EAAA,OAAA,EAAA,MAAA;;UA5XxB,aAAA,CA8XE;MAEA,EAAA,MAAA;UACD,EAAA;IACC,IAAA,EAAA,MAAA;IACY,WAAK,CAAA,EAAA,MAAA;IAC1B,UAAA,CAAA,EAAA,MAAA;EAAO,CAAA;AA2SV;UAtqBU,aAAA,CAsqBgC;OACnC,EAAA,MAAK;UACL,EAtqBK,aAsqBA,EAAA;QAEA,CAAA,EAAA,OAAA;SACD,CAAA,EAAA;IACC,WAAA,CAAA,EAAA,MAAA;IACY,WAAK,CAAA,EAAA,MAAA;;EACnB,KAAA,CAAA,EAzqBA,aAyqBA,EAAA;;iBAjpBM,yBAAA,MAA+B,gBAAgB;iBAsVzC,YAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B;iBA2SmB,oBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B"}
package/dist/ollama.js CHANGED
@@ -1,4 +1,4 @@
1
- import { flattenHeaders, getTestId, isContentWithToolCallsResponse, isErrorResponse, isTextResponse, isToolCallResponse } from "./helpers.js";
1
+ import { flattenHeaders, getTestId, isContentWithToolCallsResponse, isErrorResponse, isTextResponse, isToolCallResponse, resolveResponse } from "./helpers.js";
2
2
  import { matchFixture } from "./router.js";
3
3
  import { writeErrorResponse } from "./sse-writer.js";
4
4
  import { createInterruptionSignal } from "./interruption.js";
@@ -326,10 +326,10 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
326
326
  path: urlPath,
327
327
  headers: flattenHeaders(req.headers),
328
328
  body: completionReq
329
- }, defaults.registry, defaults.logger)) return;
329
+ }, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
330
330
  if (!fixture) {
331
331
  if (defaults.record) {
332
- if (await proxyAndRecord(req, res, completionReq, "ollama", urlPath, fixtures, defaults, raw)) {
332
+ if (await proxyAndRecord(req, res, completionReq, "ollama", urlPath, fixtures, defaults, raw) !== "not_configured") {
333
333
  journal.add({
334
334
  method: req.method ?? "POST",
335
335
  path: urlPath,
@@ -363,7 +363,7 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
363
363
  } }));
364
364
  return;
365
365
  }
366
- const response = fixture.response;
366
+ const response = await resolveResponse(fixture, completionReq);
367
367
  const latency = fixture.latency ?? defaults.latency;
368
368
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
369
369
  const streaming = ollamaReq.stream !== false;
@@ -546,10 +546,10 @@ async function handleOllamaGenerate(req, res, raw, fixtures, journal, defaults,
546
546
  path: urlPath,
547
547
  headers: flattenHeaders(req.headers),
548
548
  body: completionReq
549
- }, defaults.registry, defaults.logger)) return;
549
+ }, fixture ? "fixture" : "proxy", defaults.registry, defaults.logger)) return;
550
550
  if (!fixture) {
551
551
  if (defaults.record) {
552
- if (await proxyAndRecord(req, res, completionReq, "ollama", urlPath, fixtures, defaults, raw)) {
552
+ if (await proxyAndRecord(req, res, completionReq, "ollama", urlPath, fixtures, defaults, raw) !== "not_configured") {
553
553
  journal.add({
554
554
  method: req.method ?? "POST",
555
555
  path: urlPath,
@@ -583,7 +583,7 @@ async function handleOllamaGenerate(req, res, raw, fixtures, journal, defaults,
583
583
  } }));
584
584
  return;
585
585
  }
586
- const response = fixture.response;
586
+ const response = await resolveResponse(fixture, completionReq);
587
587
  const latency = fixture.latency ?? defaults.latency;
588
588
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
589
589
  const streaming = generateReq.stream !== false;
@@ -1 +1 @@
1
- {"version":3,"file":"ollama.js","names":[],"sources":["../src/ollama.ts"],"sourcesContent":["/**\n * Ollama API endpoint support.\n *\n * Translates incoming /api/chat and /api/generate requests into the\n * ChatCompletionRequest format used by the fixture router, and converts\n * fixture responses back into Ollama's NDJSON streaming or non-streaming format.\n *\n * Key differences from OpenAI:\n * - Ollama defaults to stream: true (opposite of OpenAI)\n * - Streaming uses NDJSON, not SSE\n * - Tool call arguments are objects, not JSON strings\n * - Tool calls have no id field\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n flattenHeaders,\n getTestId,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { writeNDJSONStream } from \"./ndjson-writer.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Ollama request types ────────────────────────────────────────────────────\n\ninterface OllamaMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string;\n}\n\ninterface OllamaToolDef {\n type: string;\n function: {\n name: string;\n description?: string;\n parameters?: object;\n };\n}\n\ninterface OllamaRequest {\n model: string;\n messages: OllamaMessage[];\n stream?: boolean; // default true!\n options?: { temperature?: number; num_predict?: number };\n tools?: OllamaToolDef[];\n}\n\ninterface OllamaGenerateRequest {\n model: string;\n prompt: string;\n stream?: boolean; // default true!\n options?: { temperature?: number; num_predict?: number };\n}\n\n// ─── Duration fields (zeroed, required on final/non-streaming responses) ────\n\nconst DURATION_FIELDS = {\n done_reason: \"stop\" as const,\n total_duration: 0,\n load_duration: 0,\n prompt_eval_count: 0,\n prompt_eval_duration: 0,\n eval_count: 0,\n eval_duration: 0,\n};\n\n// ─── Input conversion: Ollama → ChatCompletionRequest ────────────────────────\n\nexport function ollamaToCompletionRequest(req: OllamaRequest): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n for (const msg of req.messages) {\n messages.push({\n role: msg.role as ChatMessage[\"role\"],\n content: msg.content,\n });\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n tools = req.tools.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n return {\n model: req.model,\n messages,\n stream: req.stream ?? true,\n temperature: req.options?.temperature,\n max_tokens: req.options?.num_predict,\n tools,\n };\n}\n\nfunction ollamaGenerateToCompletionRequest(req: OllamaGenerateRequest): ChatCompletionRequest {\n return {\n model: req.model,\n messages: [{ role: \"user\", content: req.prompt }],\n stream: req.stream ?? true,\n temperature: req.options?.temperature,\n max_tokens: req.options?.num_predict,\n };\n}\n\n// ─── Response builders: /api/chat ────────────────────────────────────────────\n\nfunction buildOllamaChatTextChunks(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n): object[] {\n const chunks: object[] = [];\n\n // Reasoning chunks (before content)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\n\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n message: { role: \"assistant\", content: slice },\n done: false,\n });\n }\n\n // Final chunk with done: true and all duration fields\n chunks.push({\n model,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatTextResponse(content: string, model: string, reasoning?: string): object {\n return {\n model,\n message: {\n role: \"assistant\",\n content,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\nfunction buildOllamaChatToolCallChunks(\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n): object[] {\n const ollamaToolCalls = toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n // Tool calls are sent in a single chunk (no streaming of individual args)\n const chunks: object[] = [];\n chunks.push({\n model,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: ollamaToolCalls,\n },\n done: false,\n });\n\n // Final chunk\n chunks.push({\n model,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatToolCallResponse(\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n): object {\n const ollamaToolCalls = toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n return {\n model,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: ollamaToolCalls,\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\n// ─── Response builders: /api/chat — content + tool calls ────────────────────\n\nfunction buildOllamaChatContentWithToolCallsChunks(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n logger: Logger,\n): object[] {\n const chunks: object[] = [];\n\n // Content chunks first\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n message: { role: \"assistant\", content: slice },\n done: false,\n });\n }\n\n // Tool calls in a single chunk (same as tool-call-only path)\n const ollamaToolCalls = toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n chunks.push({\n model,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: ollamaToolCalls,\n },\n done: false,\n });\n\n // Final chunk\n chunks.push({\n model,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n): object {\n const ollamaToolCalls = toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n return {\n model,\n message: {\n role: \"assistant\",\n content,\n tool_calls: ollamaToolCalls,\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\n// ─── Response builders: /api/generate ────────────────────────────────────────\n\nfunction buildOllamaGenerateTextChunks(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n): object[] {\n const chunks: object[] = [];\n const createdAt = new Date().toISOString();\n\n // Reasoning chunks (before content)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n response: \"\",\n reasoning_content: slice,\n done: false,\n });\n }\n }\n\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n response: slice,\n done: false,\n });\n }\n\n // Final chunk\n chunks.push({\n model,\n created_at: createdAt,\n response: \"\",\n done: true,\n ...DURATION_FIELDS,\n context: [],\n });\n\n return chunks;\n}\n\nfunction buildOllamaGenerateTextResponse(\n content: string,\n model: string,\n reasoning?: string,\n): object {\n return {\n model,\n created_at: new Date().toISOString(),\n response: content,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n done: true,\n ...DURATION_FIELDS,\n context: [],\n };\n}\n\n// ─── Request handler: /api/chat ──────────────────────────────────────────────\n\nexport async function handleOllama(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n const urlPath = req.url ?? \"/api/chat\";\n\n let ollamaReq: OllamaRequest;\n try {\n ollamaReq = JSON.parse(raw) as OllamaRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!ollamaReq.messages || !Array.isArray(ollamaReq.messages)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Invalid request: messages array is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = ollamaToCompletionRequest(ollamaReq);\n completionReq._endpointType = \"chat\";\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n logger.debug(`No fixture matched for request`);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const proxied = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"ollama\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${urlPath}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const response = fixture.response;\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Ollama defaults to streaming when stream is absent or true\n const streaming = ollamaReq.stream !== false;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, JSON.stringify(response));\n return;\n }\n\n // Content + tool calls response (must be checked before text/tool-only branches)\n if (isContentWithToolCallsResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildOllamaChatContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n completionReq.model,\n logger,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatContentWithToolCallsChunks(\n response.content,\n response.toolCalls,\n completionReq.model,\n chunkSize,\n logger,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildOllamaChatTextResponse(\n response.content,\n completionReq.model,\n response.reasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatTextChunks(\n response.content,\n completionReq.model,\n chunkSize,\n response.reasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildOllamaChatToolCallResponse(response.toolCalls, completionReq.model, logger);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatToolCallChunks(response.toolCalls, completionReq.model, logger);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n type: \"server_error\",\n },\n }),\n );\n}\n\n// ─── Request handler: /api/generate ──────────────────────────────────────────\n\nexport async function handleOllamaGenerate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n setCorsHeaders(res);\n\n const urlPath = req.url ?? \"/api/generate\";\n\n let generateReq: OllamaGenerateRequest;\n try {\n generateReq = JSON.parse(raw) as OllamaGenerateRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!generateReq.prompt || typeof generateReq.prompt !== \"string\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Invalid request: prompt field is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = ollamaGenerateToCompletionRequest(generateReq);\n completionReq._endpointType = \"chat\";\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const proxied = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"ollama\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n defaults.logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${urlPath}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const response = fixture.response;\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Ollama defaults to streaming when stream is absent or true\n const streaming = generateReq.stream !== false;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, JSON.stringify(response));\n return;\n }\n\n // Text response (only type supported for /api/generate)\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildOllamaGenerateTextResponse(\n response.content,\n completionReq.model,\n response.reasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaGenerateTextChunks(\n response.content,\n completionReq.model,\n chunkSize,\n response.reasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call fixtures matched but not supported on /api/generate\n if (isToolCallResponse(response) || isContentWithToolCallsResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 400, fixture },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Tool call fixtures are not supported on /api/generate — use /api/chat instead\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n type: \"server_error\",\n },\n }),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ollama.js","names":[],"sources":["../src/ollama.ts"],"sourcesContent":["/**\n * Ollama API endpoint support.\n *\n * Translates incoming /api/chat and /api/generate requests into the\n * ChatCompletionRequest format used by the fixture router, and converts\n * fixture responses back into Ollama's NDJSON streaming or non-streaming format.\n *\n * Key differences from OpenAI:\n * - Ollama defaults to stream: true (opposite of OpenAI)\n * - Streaming uses NDJSON, not SSE\n * - Tool call arguments are objects, not JSON strings\n * - Tool calls have no id field\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport { writeNDJSONStream } from \"./ndjson-writer.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Ollama request types ────────────────────────────────────────────────────\n\ninterface OllamaMessage {\n role: \"system\" | \"user\" | \"assistant\" | \"tool\";\n content: string;\n}\n\ninterface OllamaToolDef {\n type: string;\n function: {\n name: string;\n description?: string;\n parameters?: object;\n };\n}\n\ninterface OllamaRequest {\n model: string;\n messages: OllamaMessage[];\n stream?: boolean; // default true!\n options?: { temperature?: number; num_predict?: number };\n tools?: OllamaToolDef[];\n}\n\ninterface OllamaGenerateRequest {\n model: string;\n prompt: string;\n stream?: boolean; // default true!\n options?: { temperature?: number; num_predict?: number };\n}\n\n// ─── Duration fields (zeroed, required on final/non-streaming responses) ────\n\nconst DURATION_FIELDS = {\n done_reason: \"stop\" as const,\n total_duration: 0,\n load_duration: 0,\n prompt_eval_count: 0,\n prompt_eval_duration: 0,\n eval_count: 0,\n eval_duration: 0,\n};\n\n// ─── Input conversion: Ollama → ChatCompletionRequest ────────────────────────\n\nexport function ollamaToCompletionRequest(req: OllamaRequest): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n for (const msg of req.messages) {\n messages.push({\n role: msg.role as ChatMessage[\"role\"],\n content: msg.content,\n });\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n tools = req.tools.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.function.name,\n description: t.function.description,\n parameters: t.function.parameters,\n },\n }));\n }\n\n return {\n model: req.model,\n messages,\n stream: req.stream ?? true,\n temperature: req.options?.temperature,\n max_tokens: req.options?.num_predict,\n tools,\n };\n}\n\nfunction ollamaGenerateToCompletionRequest(req: OllamaGenerateRequest): ChatCompletionRequest {\n return {\n model: req.model,\n messages: [{ role: \"user\", content: req.prompt }],\n stream: req.stream ?? true,\n temperature: req.options?.temperature,\n max_tokens: req.options?.num_predict,\n };\n}\n\n// ─── Response builders: /api/chat ────────────────────────────────────────────\n\nfunction buildOllamaChatTextChunks(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n): object[] {\n const chunks: object[] = [];\n\n // Reasoning chunks (before content)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\n\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n message: { role: \"assistant\", content: slice },\n done: false,\n });\n }\n\n // Final chunk with done: true and all duration fields\n chunks.push({\n model,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatTextResponse(content: string, model: string, reasoning?: string): object {\n return {\n model,\n message: {\n role: \"assistant\",\n content,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\nfunction buildOllamaChatToolCallChunks(\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n): object[] {\n const ollamaToolCalls = toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n // Tool calls are sent in a single chunk (no streaming of individual args)\n const chunks: object[] = [];\n chunks.push({\n model,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: ollamaToolCalls,\n },\n done: false,\n });\n\n // Final chunk\n chunks.push({\n model,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatToolCallResponse(\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n): object {\n const ollamaToolCalls = toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n return {\n model,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: ollamaToolCalls,\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\n// ─── Response builders: /api/chat — content + tool calls ────────────────────\n\nfunction buildOllamaChatContentWithToolCallsChunks(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n logger: Logger,\n): object[] {\n const chunks: object[] = [];\n\n // Content chunks first\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n message: { role: \"assistant\", content: slice },\n done: false,\n });\n }\n\n // Tool calls in a single chunk (same as tool-call-only path)\n const ollamaToolCalls = toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n chunks.push({\n model,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: ollamaToolCalls,\n },\n done: false,\n });\n\n // Final chunk\n chunks.push({\n model,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\nfunction buildOllamaChatContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n): object {\n const ollamaToolCalls = toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n function: {\n name: tc.name,\n arguments: argsObj,\n },\n };\n });\n\n return {\n model,\n message: {\n role: \"assistant\",\n content,\n tool_calls: ollamaToolCalls,\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\n// ─── Response builders: /api/generate ────────────────────────────────────────\n\nfunction buildOllamaGenerateTextChunks(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n): object[] {\n const chunks: object[] = [];\n const createdAt = new Date().toISOString();\n\n // Reasoning chunks (before content)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n response: \"\",\n reasoning_content: slice,\n done: false,\n });\n }\n }\n\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n response: slice,\n done: false,\n });\n }\n\n // Final chunk\n chunks.push({\n model,\n created_at: createdAt,\n response: \"\",\n done: true,\n ...DURATION_FIELDS,\n context: [],\n });\n\n return chunks;\n}\n\nfunction buildOllamaGenerateTextResponse(\n content: string,\n model: string,\n reasoning?: string,\n): object {\n return {\n model,\n created_at: new Date().toISOString(),\n response: content,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n done: true,\n ...DURATION_FIELDS,\n context: [],\n };\n}\n\n// ─── Request handler: /api/chat ──────────────────────────────────────────────\n\nexport async function handleOllama(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n const urlPath = req.url ?? \"/api/chat\";\n\n let ollamaReq: OllamaRequest;\n try {\n ollamaReq = JSON.parse(raw) as OllamaRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!ollamaReq.messages || !Array.isArray(ollamaReq.messages)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Invalid request: messages array is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = ollamaToCompletionRequest(ollamaReq);\n completionReq._endpointType = \"chat\";\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n logger.debug(`No fixture matched for request`);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"ollama\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${urlPath}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Ollama defaults to streaming when stream is absent or true\n const streaming = ollamaReq.stream !== false;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, JSON.stringify(response));\n return;\n }\n\n // Content + tool calls response (must be checked before text/tool-only branches)\n if (isContentWithToolCallsResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildOllamaChatContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n completionReq.model,\n logger,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatContentWithToolCallsChunks(\n response.content,\n response.toolCalls,\n completionReq.model,\n chunkSize,\n logger,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildOllamaChatTextResponse(\n response.content,\n completionReq.model,\n response.reasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatTextChunks(\n response.content,\n completionReq.model,\n chunkSize,\n response.reasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildOllamaChatToolCallResponse(response.toolCalls, completionReq.model, logger);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatToolCallChunks(response.toolCalls, completionReq.model, logger);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n type: \"server_error\",\n },\n }),\n );\n}\n\n// ─── Request handler: /api/generate ──────────────────────────────────────────\n\nexport async function handleOllamaGenerate(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n setCorsHeaders(res);\n\n const urlPath = req.url ?? \"/api/generate\";\n\n let generateReq: OllamaGenerateRequest;\n try {\n generateReq = JSON.parse(raw) as OllamaGenerateRequest;\n } catch {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Malformed JSON\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!generateReq.prompt || typeof generateReq.prompt !== \"string\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Invalid request: prompt field is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = ollamaGenerateToCompletionRequest(generateReq);\n completionReq._endpointType = \"chat\";\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n defaults.logger.debug(`No fixture matched for request`);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"ollama\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n defaults.logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${urlPath}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const response = await resolveResponse(fixture, completionReq);\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Ollama defaults to streaming when stream is absent or true\n const streaming = generateReq.stream !== false;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, JSON.stringify(response));\n return;\n }\n\n // Text response (only type supported for /api/generate)\n if (isTextResponse(response)) {\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildOllamaGenerateTextResponse(\n response.content,\n completionReq.model,\n response.reasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaGenerateTextChunks(\n response.content,\n completionReq.model,\n chunkSize,\n response.reasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call fixtures matched but not supported on /api/generate\n if (isToolCallResponse(response) || isContentWithToolCallsResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 400, fixture },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Tool call fixtures are not supported on /api/generate — use /api/chat instead\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n type: \"server_error\",\n },\n }),\n );\n}\n"],"mappings":"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"}
package/dist/recorder.cjs CHANGED
@@ -1,4 +1,5 @@
1
1
  const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
2
+ const require_helpers = require('./helpers.cjs');
2
3
  const require_router = require('./router.cjs');
3
4
  const require_sse_writer = require('./sse-writer.cjs');
4
5
  const require_stream_collapse = require('./stream-collapse.cjs');
@@ -34,17 +35,14 @@ const STRIP_HEADERS = new Set([
34
35
  * Proxy an unmatched request to the real upstream provider, record the
35
36
  * response as a fixture on disk and in memory, then relay the response
36
37
  * back to the original client.
37
- *
38
- * Returns `true` if the request was proxied (provider configured),
39
- * `false` if no upstream URL is configured for the given provider key.
40
38
  */
41
- async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures, defaults, rawBody) {
39
+ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures, defaults, rawBody, options) {
42
40
  const record = defaults.record;
43
- if (!record) return false;
41
+ if (!record) return "not_configured";
44
42
  const upstreamUrl = record.providers[providerKey === "gemini-interactions" ? "gemini" : providerKey];
45
43
  if (!upstreamUrl) {
46
44
  defaults.logger.warn(`No upstream URL configured for provider "${providerKey}" — cannot proxy`);
47
- return false;
45
+ return "not_configured";
48
46
  }
49
47
  const fixturePath = record.fixturePath ?? "./fixtures/recorded";
50
48
  let target;
@@ -56,7 +54,7 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
56
54
  message: `Invalid upstream URL: ${upstreamUrl}`,
57
55
  type: "proxy_error"
58
56
  } }));
59
- return true;
57
+ return "relayed";
60
58
  }
61
59
  defaults.logger.warn(`NO FIXTURE MATCH — proxying to ${upstreamUrl}${pathname}`);
62
60
  const forwardHeaders = {};
@@ -86,7 +84,7 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
86
84
  type: "proxy_error"
87
85
  } }));
88
86
  } else res.end();
89
- return true;
87
+ return "relayed";
90
88
  }
91
89
  const contentType = upstreamHeaders["content-type"];
92
90
  const ctString = Array.isArray(contentType) ? contentType.join(", ") : contentType ?? "";
@@ -137,17 +135,35 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
137
135
  } catch {
138
136
  defaults.logger.warn("Upstream response is not valid JSON — saving as error fixture");
139
137
  }
140
- let encodingFormat;
141
- try {
142
- encodingFormat = rawBody ? JSON.parse(rawBody).encoding_format : void 0;
143
- } catch (err) {
144
- defaults.logger.debug(`Could not parse encoding_format from raw body: ${err instanceof Error ? err.message : "unknown error"}`);
138
+ if (request._endpointType === "fal" && parsedResponse !== null) {
139
+ const obj = parsedResponse;
140
+ if (typeof obj.error === "object" && obj.error !== null && typeof obj.error.message === "string") {
141
+ const err = obj.error;
142
+ fixtureResponse = {
143
+ error: {
144
+ message: String(err.message ?? "Unknown error"),
145
+ type: String(err.type ?? "api_error"),
146
+ code: err.code ? String(err.code) : void 0
147
+ },
148
+ status: upstreamStatus
149
+ };
150
+ } else fixtureResponse = {
151
+ json: parsedResponse,
152
+ status: upstreamStatus
153
+ };
154
+ } else {
155
+ let encodingFormat;
156
+ try {
157
+ encodingFormat = rawBody ? JSON.parse(rawBody).encoding_format : void 0;
158
+ } catch (err) {
159
+ defaults.logger.debug(`Could not parse encoding_format from raw body: ${err instanceof Error ? err.message : "unknown error"}`);
160
+ }
161
+ fixtureResponse = buildFixtureResponse(parsedResponse, upstreamStatus, encodingFormat);
145
162
  }
146
- fixtureResponse = buildFixtureResponse(parsedResponse, upstreamStatus, encodingFormat);
147
163
  }
148
164
  if (clientDisconnected) {
149
165
  defaults.logger.warn("Client disconnected mid-stream — skipping fixture save to avoid truncated data");
150
- return true;
166
+ return "relayed";
151
167
  }
152
168
  const fixtureMatch = buildFixtureMatch(defaults.requestTransform ? defaults.requestTransform(request) : request);
153
169
  const fixture = {
@@ -158,15 +174,37 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
158
174
  const isEmptyMatch = matchValues.length === 0 || matchValues.every((v) => v === void 0);
159
175
  if (isEmptyMatch) defaults.logger.warn("Recorded fixture has empty match criteria — skipping in-memory registration");
160
176
  if (!defaults.record?.proxyOnly) {
161
- const filename = `${providerKey}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${node_crypto.randomUUID().slice(0, 8)}.json`;
162
- const filepath = node_path.join(fixturePath, filename);
177
+ const testId = require_helpers.getTestId(req);
178
+ let isSnapshotMode = testId !== "__default__";
179
+ let filepath;
180
+ let mergeExisting = false;
181
+ if (isSnapshotMode) {
182
+ const slug = require_helpers.slugifyTestId(testId);
183
+ if (!slug) isSnapshotMode = false;
184
+ else {
185
+ const dir = node_path.join(fixturePath, slug);
186
+ filepath = node_path.join(dir, `${providerKey}.json`);
187
+ mergeExisting = true;
188
+ }
189
+ }
190
+ if (!isSnapshotMode) {
191
+ const filename = `${providerKey}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${node_crypto.randomUUID().slice(0, 8)}.json`;
192
+ filepath = node_path.join(fixturePath, filename);
193
+ }
163
194
  let writtenToDisk = false;
164
195
  try {
165
- node_fs.mkdirSync(fixturePath, { recursive: true });
196
+ if (isSnapshotMode) node_fs.mkdirSync(node_path.dirname(filepath), { recursive: true });
197
+ else node_fs.mkdirSync(fixturePath, { recursive: true });
166
198
  const warnings = [];
167
199
  if (isEmptyMatch) warnings.push("Empty match criteria — this fixture will not match any request");
168
200
  if (collapsed?.truncated) warnings.push("Stream response was truncated — fixture may be incomplete");
169
- const fileContent = { fixtures: [fixture] };
201
+ let fileContent;
202
+ if (mergeExisting && node_fs.existsSync(filepath)) try {
203
+ fileContent = { fixtures: [...JSON.parse(node_fs.readFileSync(filepath, "utf-8")).fixtures ?? [], fixture] };
204
+ } catch {
205
+ fileContent = { fixtures: [fixture] };
206
+ }
207
+ else fileContent = { fixtures: [fixture] };
170
208
  if (warnings.length > 0) fileContent._warning = warnings.join("; ");
171
209
  node_fs.writeFileSync(filepath, JSON.stringify(fileContent, null, 2), "utf-8");
172
210
  writtenToDisk = true;
@@ -181,14 +219,23 @@ async function proxyAndRecord(req, res, request, providerKey, pathname, fixtures
181
219
  defaults.logger.warn(`Response recorded → ${filepath}`);
182
220
  } else defaults.logger.warn(`Response relayed but NOT saved to disk — see error above`);
183
221
  } else defaults.logger.info(`Proxied ${providerKey} request (proxy-only mode)`);
184
- if (!streamedToClient) {
222
+ if (streamedToClient) {
223
+ if (options?.beforeWriteResponse && options.onHookBypassed) options.onHookBypassed("sse_streamed");
224
+ } else {
225
+ if (options?.beforeWriteResponse) {
226
+ if (await options.beforeWriteResponse({
227
+ status: upstreamStatus,
228
+ contentType: ctString,
229
+ body: rawBuffer
230
+ })) return "handled_by_hook";
231
+ }
185
232
  const relayHeaders = {};
186
233
  if (ctString) relayHeaders["Content-Type"] = ctString;
187
234
  res.writeHead(upstreamStatus, relayHeaders);
188
235
  const isAudioRelay = ctString.toLowerCase().startsWith("audio/");
189
236
  res.end(isBinaryStream || isAudioRelay ? rawBuffer : upstreamBody);
190
237
  }
191
- return true;
238
+ return "relayed";
192
239
  }
193
240
  function makeUpstreamRequest(target, headers, body, clientRes) {
194
241
  return new Promise((resolve, reject) => {
@@ -582,6 +629,7 @@ function buildFixtureMatch(request) {
582
629
  const text = require_router.getTextContent(lastUser.content);
583
630
  if (text) match.userMessage = text;
584
631
  }
632
+ if (request._endpointType === "fal" && request.model) match.model = request.model;
585
633
  return match;
586
634
  }
587
635