@copilotkit/aimock 1.43.0 → 1.44.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 (290) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +15 -0
  4. package/README.md +8 -2
  5. package/dist/agui-handler.d.ts.map +1 -1
  6. package/dist/agui-types.d.ts.map +1 -1
  7. package/dist/bedrock-converse.cjs +32 -4
  8. package/dist/bedrock-converse.cjs.map +1 -1
  9. package/dist/bedrock-converse.d.cts.map +1 -1
  10. package/dist/bedrock-converse.d.ts.map +1 -1
  11. package/dist/bedrock-converse.js +32 -4
  12. package/dist/bedrock-converse.js.map +1 -1
  13. package/dist/chaos.cjs +8 -5
  14. package/dist/chaos.cjs.map +1 -1
  15. package/dist/chaos.d.cts.map +1 -1
  16. package/dist/chaos.d.ts.map +1 -1
  17. package/dist/chaos.js +9 -6
  18. package/dist/chaos.js.map +1 -1
  19. package/dist/cli.cjs +89 -7
  20. package/dist/cli.cjs.map +1 -1
  21. package/dist/cli.js +91 -9
  22. package/dist/cli.js.map +1 -1
  23. package/dist/cohere.cjs +27 -1
  24. package/dist/cohere.cjs.map +1 -1
  25. package/dist/cohere.js +27 -1
  26. package/dist/cohere.js.map +1 -1
  27. package/dist/config-loader.cjs +1 -1
  28. package/dist/config-loader.d.ts.map +1 -1
  29. package/dist/config-loader.js +1 -1
  30. package/dist/constants.cjs +8 -0
  31. package/dist/constants.cjs.map +1 -1
  32. package/dist/constants.d.cts +6 -2
  33. package/dist/constants.d.ts +6 -2
  34. package/dist/constants.js +8 -1
  35. package/dist/constants.js.map +1 -1
  36. package/dist/echo-text.cjs +65 -0
  37. package/dist/echo-text.cjs.map +1 -0
  38. package/dist/echo-text.d.cts +22 -0
  39. package/dist/echo-text.d.cts.map +1 -0
  40. package/dist/echo-text.d.ts +22 -0
  41. package/dist/echo-text.d.ts.map +1 -0
  42. package/dist/echo-text.js +65 -0
  43. package/dist/echo-text.js.map +1 -0
  44. package/dist/embeddings.cjs +18 -1
  45. package/dist/embeddings.cjs.map +1 -1
  46. package/dist/embeddings.d.cts +1 -1
  47. package/dist/embeddings.d.cts.map +1 -1
  48. package/dist/embeddings.d.ts +1 -1
  49. package/dist/embeddings.d.ts.map +1 -1
  50. package/dist/embeddings.js +18 -1
  51. package/dist/embeddings.js.map +1 -1
  52. package/dist/files.cjs +9 -11
  53. package/dist/files.cjs.map +1 -1
  54. package/dist/files.d.cts.map +1 -1
  55. package/dist/files.d.ts.map +1 -1
  56. package/dist/files.js +10 -13
  57. package/dist/files.js.map +1 -1
  58. package/dist/fine-tuning.cjs +1 -19
  59. package/dist/fine-tuning.cjs.map +1 -1
  60. package/dist/fine-tuning.d.cts.map +1 -1
  61. package/dist/fine-tuning.d.ts.map +1 -1
  62. package/dist/fine-tuning.js +2 -20
  63. package/dist/fine-tuning.js.map +1 -1
  64. package/dist/fixture-loader-services.cjs +116 -0
  65. package/dist/fixture-loader-services.cjs.map +1 -0
  66. package/dist/fixture-loader-services.d.cts +48 -0
  67. package/dist/fixture-loader-services.d.cts.map +1 -0
  68. package/dist/fixture-loader-services.d.ts +48 -0
  69. package/dist/fixture-loader-services.d.ts.map +1 -0
  70. package/dist/fixture-loader-services.js +114 -0
  71. package/dist/fixture-loader-services.js.map +1 -0
  72. package/dist/fixture-loader.cjs +172 -11
  73. package/dist/fixture-loader.cjs.map +1 -1
  74. package/dist/fixture-loader.d.cts +95 -1
  75. package/dist/fixture-loader.d.cts.map +1 -1
  76. package/dist/fixture-loader.d.ts +95 -1
  77. package/dist/fixture-loader.d.ts.map +1 -1
  78. package/dist/fixture-loader.js +168 -12
  79. package/dist/fixture-loader.js.map +1 -1
  80. package/dist/gemini-embeddings.cjs +42 -2
  81. package/dist/gemini-embeddings.cjs.map +1 -1
  82. package/dist/gemini-embeddings.js +43 -3
  83. package/dist/gemini-embeddings.js.map +1 -1
  84. package/dist/gemini-interactions.cjs +38 -0
  85. package/dist/gemini-interactions.cjs.map +1 -1
  86. package/dist/gemini-interactions.d.cts.map +1 -1
  87. package/dist/gemini-interactions.d.ts.map +1 -1
  88. package/dist/gemini-interactions.js +38 -0
  89. package/dist/gemini-interactions.js.map +1 -1
  90. package/dist/gemini.cjs +8 -1
  91. package/dist/gemini.cjs.map +1 -1
  92. package/dist/gemini.d.cts.map +1 -1
  93. package/dist/gemini.d.ts.map +1 -1
  94. package/dist/gemini.js +8 -1
  95. package/dist/gemini.js.map +1 -1
  96. package/dist/helpers.cjs +276 -5
  97. package/dist/helpers.cjs.map +1 -1
  98. package/dist/helpers.d.cts.map +1 -1
  99. package/dist/helpers.d.ts.map +1 -1
  100. package/dist/helpers.js +275 -7
  101. package/dist/helpers.js.map +1 -1
  102. package/dist/images.cjs +3 -2
  103. package/dist/images.cjs.map +1 -1
  104. package/dist/images.js +3 -2
  105. package/dist/images.js.map +1 -1
  106. package/dist/index.cjs +30 -1
  107. package/dist/index.d.cts +8 -3
  108. package/dist/index.d.ts +8 -3
  109. package/dist/index.js +9 -3
  110. package/dist/jest.cjs +15 -10
  111. package/dist/jest.cjs.map +1 -1
  112. package/dist/jest.d.cts +4 -2
  113. package/dist/jest.d.cts.map +1 -1
  114. package/dist/jest.d.ts +4 -2
  115. package/dist/jest.d.ts.map +1 -1
  116. package/dist/jest.js +16 -11
  117. package/dist/jest.js.map +1 -1
  118. package/dist/journal.cjs +20 -0
  119. package/dist/journal.cjs.map +1 -1
  120. package/dist/journal.d.cts +16 -0
  121. package/dist/journal.d.cts.map +1 -1
  122. package/dist/journal.d.ts +16 -0
  123. package/dist/journal.d.ts.map +1 -1
  124. package/dist/journal.js +20 -0
  125. package/dist/journal.js.map +1 -1
  126. package/dist/llmock.cjs +82 -10
  127. package/dist/llmock.cjs.map +1 -1
  128. package/dist/llmock.d.cts +35 -0
  129. package/dist/llmock.d.cts.map +1 -1
  130. package/dist/llmock.d.ts +35 -0
  131. package/dist/llmock.d.ts.map +1 -1
  132. package/dist/llmock.js +83 -11
  133. package/dist/llmock.js.map +1 -1
  134. package/dist/mcp-fakes-mount.cjs +216 -0
  135. package/dist/mcp-fakes-mount.cjs.map +1 -0
  136. package/dist/mcp-fakes-mount.js +210 -0
  137. package/dist/mcp-fakes-mount.js.map +1 -0
  138. package/dist/mcp-fakes.cjs +1619 -0
  139. package/dist/mcp-fakes.cjs.map +1 -0
  140. package/dist/mcp-fakes.d.cts +101 -0
  141. package/dist/mcp-fakes.d.cts.map +1 -0
  142. package/dist/mcp-fakes.d.ts +101 -0
  143. package/dist/mcp-fakes.d.ts.map +1 -0
  144. package/dist/mcp-fakes.js +1614 -0
  145. package/dist/mcp-fakes.js.map +1 -0
  146. package/dist/mcp-handler.cjs +331 -17
  147. package/dist/mcp-handler.cjs.map +1 -1
  148. package/dist/mcp-handler.js +330 -18
  149. package/dist/mcp-handler.js.map +1 -1
  150. package/dist/mcp-mock.cjs +217 -49
  151. package/dist/mcp-mock.cjs.map +1 -1
  152. package/dist/mcp-mock.d.cts +66 -1
  153. package/dist/mcp-mock.d.cts.map +1 -1
  154. package/dist/mcp-mock.d.ts +66 -1
  155. package/dist/mcp-mock.d.ts.map +1 -1
  156. package/dist/mcp-mock.js +218 -50
  157. package/dist/mcp-mock.js.map +1 -1
  158. package/dist/mcp-stub.cjs +5 -1
  159. package/dist/mcp-stub.d.cts +3 -1
  160. package/dist/mcp-stub.d.ts +3 -1
  161. package/dist/mcp-stub.js +3 -1
  162. package/dist/mcp-types.d.cts +6 -0
  163. package/dist/mcp-types.d.ts +6 -0
  164. package/dist/message-text.cjs +192 -0
  165. package/dist/message-text.cjs.map +1 -0
  166. package/dist/message-text.d.cts +31 -0
  167. package/dist/message-text.d.cts.map +1 -0
  168. package/dist/message-text.d.ts +31 -0
  169. package/dist/message-text.d.ts.map +1 -0
  170. package/dist/message-text.js +184 -0
  171. package/dist/message-text.js.map +1 -0
  172. package/dist/messages.cjs +38 -0
  173. package/dist/messages.cjs.map +1 -1
  174. package/dist/messages.js +38 -0
  175. package/dist/messages.js.map +1 -1
  176. package/dist/metrics.cjs +46 -66
  177. package/dist/metrics.cjs.map +1 -1
  178. package/dist/metrics.d.cts +14 -6
  179. package/dist/metrics.d.cts.map +1 -1
  180. package/dist/metrics.d.ts +14 -6
  181. package/dist/metrics.d.ts.map +1 -1
  182. package/dist/metrics.js +32 -40
  183. package/dist/metrics.js.map +1 -1
  184. package/dist/ollama.cjs +5 -1
  185. package/dist/ollama.cjs.map +1 -1
  186. package/dist/ollama.d.cts.map +1 -1
  187. package/dist/ollama.d.ts.map +1 -1
  188. package/dist/ollama.js +5 -1
  189. package/dist/ollama.js.map +1 -1
  190. package/dist/openapi.cjs +3405 -0
  191. package/dist/openapi.cjs.map +1 -0
  192. package/dist/openapi.d.cts +30 -0
  193. package/dist/openapi.d.cts.map +1 -0
  194. package/dist/openapi.d.ts +30 -0
  195. package/dist/openapi.d.ts.map +1 -0
  196. package/dist/openapi.js +3404 -0
  197. package/dist/openapi.js.map +1 -0
  198. package/dist/openrouter-video.cjs +4 -4
  199. package/dist/openrouter-video.cjs.map +1 -1
  200. package/dist/openrouter-video.d.cts.map +1 -1
  201. package/dist/openrouter-video.d.ts.map +1 -1
  202. package/dist/openrouter-video.js +4 -4
  203. package/dist/openrouter-video.js.map +1 -1
  204. package/dist/recorder.cjs +9 -1
  205. package/dist/recorder.cjs.map +1 -1
  206. package/dist/recorder.d.cts.map +1 -1
  207. package/dist/recorder.d.ts.map +1 -1
  208. package/dist/recorder.js +9 -1
  209. package/dist/recorder.js.map +1 -1
  210. package/dist/responses.cjs +43 -4
  211. package/dist/responses.cjs.map +1 -1
  212. package/dist/responses.d.cts.map +1 -1
  213. package/dist/responses.d.ts.map +1 -1
  214. package/dist/responses.js +43 -4
  215. package/dist/responses.js.map +1 -1
  216. package/dist/route-registry.cjs +951 -0
  217. package/dist/route-registry.cjs.map +1 -0
  218. package/dist/route-registry.d.cts +33 -0
  219. package/dist/route-registry.d.cts.map +1 -0
  220. package/dist/route-registry.d.ts +33 -0
  221. package/dist/route-registry.d.ts.map +1 -0
  222. package/dist/route-registry.js +866 -0
  223. package/dist/route-registry.js.map +1 -0
  224. package/dist/router.cjs +1 -1
  225. package/dist/router.cjs.map +1 -1
  226. package/dist/router.js +1 -1
  227. package/dist/router.js.map +1 -1
  228. package/dist/server.cjs +590 -191
  229. package/dist/server.cjs.map +1 -1
  230. package/dist/server.d.cts +4 -1
  231. package/dist/server.d.cts.map +1 -1
  232. package/dist/server.d.ts +4 -1
  233. package/dist/server.d.ts.map +1 -1
  234. package/dist/server.js +498 -99
  235. package/dist/server.js.map +1 -1
  236. package/dist/speech.cjs +4 -2
  237. package/dist/speech.cjs.map +1 -1
  238. package/dist/speech.js +4 -2
  239. package/dist/speech.js.map +1 -1
  240. package/dist/suite.cjs +1 -1
  241. package/dist/suite.js +1 -1
  242. package/dist/transcription.cjs +2 -2
  243. package/dist/transcription.cjs.map +1 -1
  244. package/dist/transcription.d.cts.map +1 -1
  245. package/dist/transcription.d.ts.map +1 -1
  246. package/dist/transcription.js +2 -2
  247. package/dist/transcription.js.map +1 -1
  248. package/dist/types.cjs +18 -0
  249. package/dist/types.cjs.map +1 -0
  250. package/dist/types.d.cts +202 -2
  251. package/dist/types.d.cts.map +1 -1
  252. package/dist/types.d.ts +202 -2
  253. package/dist/types.d.ts.map +1 -1
  254. package/dist/types.js +17 -0
  255. package/dist/types.js.map +1 -0
  256. package/dist/validate-cli.cjs +89 -31
  257. package/dist/validate-cli.cjs.map +1 -1
  258. package/dist/validate-cli.js +91 -33
  259. package/dist/validate-cli.js.map +1 -1
  260. package/dist/vector-stores.cjs +1180 -0
  261. package/dist/vector-stores.cjs.map +1 -0
  262. package/dist/vector-stores.d.cts +106 -0
  263. package/dist/vector-stores.d.cts.map +1 -0
  264. package/dist/vector-stores.d.ts +106 -0
  265. package/dist/vector-stores.d.ts.map +1 -0
  266. package/dist/vector-stores.js +1164 -0
  267. package/dist/vector-stores.js.map +1 -0
  268. package/dist/vector-types.d.cts.map +1 -1
  269. package/dist/vector-types.d.ts.map +1 -1
  270. package/dist/video.cjs +19 -0
  271. package/dist/video.cjs.map +1 -1
  272. package/dist/video.d.cts.map +1 -1
  273. package/dist/video.d.ts.map +1 -1
  274. package/dist/video.js +19 -0
  275. package/dist/video.js.map +1 -1
  276. package/dist/vitest.cjs +15 -10
  277. package/dist/vitest.cjs.map +1 -1
  278. package/dist/vitest.d.cts +4 -2
  279. package/dist/vitest.d.cts.map +1 -1
  280. package/dist/vitest.d.ts +4 -2
  281. package/dist/vitest.d.ts.map +1 -1
  282. package/dist/vitest.js +16 -11
  283. package/dist/vitest.js.map +1 -1
  284. package/dist/ws-gemini-live.d.ts +2 -2
  285. package/dist/ws-realtime.d.ts +2 -2
  286. package/dist/ws-responses.cjs +14 -2
  287. package/dist/ws-responses.cjs.map +1 -1
  288. package/dist/ws-responses.js +14 -2
  289. package/dist/ws-responses.js.map +1 -1
  290. package/package.json +4 -1
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.js","names":[],"sources":["../src/metrics.ts"],"sourcesContent":["/**\n * Lightweight Prometheus metrics registry for LLMock.\n *\n * Zero external dependencies — implements counters, histograms, and gauges\n * with Prometheus text exposition format serialization.\n */\n\n// ---------------------------------------------------------------------------\n// Public interface\n// ---------------------------------------------------------------------------\n\nexport interface MetricsRegistry {\n incrementCounter(name: string, labels: Record<string, string>): void;\n observeHistogram(name: string, labels: Record<string, string>, value: number): void;\n setGauge(name: string, labels: Record<string, string>, value: number): void;\n serialize(): string;\n reset(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Histogram bucket boundaries (Prometheus default-ish)\n// ---------------------------------------------------------------------------\n\nconst HISTOGRAM_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Build a stable label key string for map lookups: `label1=\"v1\",label2=\"v2\"` */\nfunction labelKey(labels: Record<string, string>): string {\n const entries = Object.entries(labels).sort(([a], [b]) => a.localeCompare(b));\n if (entries.length === 0) return \"\";\n return entries.map(([k, v]) => `${k}=\"${escapeLabelValue(v)}\"`).join(\",\");\n}\n\n/** Escape a label value per Prometheus text exposition format. */\nfunction escapeLabelValue(v: string): string {\n return v.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/\\n/g, \"\\\\n\");\n}\n\n/** Format labels for Prometheus output: `{label1=\"v1\",label2=\"v2\"}` */\nfunction formatLabels(labels: Record<string, string>): string {\n return `{${labelKey(labels)}}`;\n}\n\n// ---------------------------------------------------------------------------\n// Internal metric storage types\n// ---------------------------------------------------------------------------\n\ninterface CounterData {\n type: \"counter\";\n /** Map from labelKey → value */\n series: Map<string, { labels: Record<string, string>; value: number }>;\n}\n\ninterface HistogramData {\n type: \"histogram\";\n /** Map from labelKey → bucket counts, sum, count */\n series: Map<\n string,\n {\n labels: Record<string, string>;\n bucketCounts: number[]; // one per HISTOGRAM_BUCKETS entry\n sum: number;\n count: number;\n }\n >;\n}\n\ninterface GaugeData {\n type: \"gauge\";\n /** Map from labelKey → value */\n series: Map<string, { labels: Record<string, string>; value: number }>;\n}\n\ntype MetricData = CounterData | HistogramData | GaugeData;\n\n// ---------------------------------------------------------------------------\n// Registry implementation\n// ---------------------------------------------------------------------------\n\nexport function createMetricsRegistry(): MetricsRegistry {\n /** Ordered map: metric name → data. Insertion order preserved for stable output. */\n const metrics = new Map<string, MetricData>();\n\n function getOrCreateCounter(name: string): CounterData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"counter\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"counter\") throw new Error(`Metric ${name} is not a counter`);\n return data as CounterData;\n }\n\n function getOrCreateHistogram(name: string): HistogramData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"histogram\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"histogram\") throw new Error(`Metric ${name} is not a histogram`);\n return data as HistogramData;\n }\n\n function getOrCreateGauge(name: string): GaugeData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"gauge\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"gauge\") throw new Error(`Metric ${name} is not a gauge`);\n return data as GaugeData;\n }\n\n return {\n incrementCounter(name: string, labels: Record<string, string>): void {\n const counter = getOrCreateCounter(name);\n const key = labelKey(labels);\n const existing = counter.series.get(key);\n if (existing) {\n existing.value += 1;\n } else {\n counter.series.set(key, { labels, value: 1 });\n }\n },\n\n observeHistogram(name: string, labels: Record<string, string>, value: number): void {\n const histogram = getOrCreateHistogram(name);\n const key = labelKey(labels);\n let existing = histogram.series.get(key);\n if (!existing) {\n existing = {\n labels,\n bucketCounts: new Array(HISTOGRAM_BUCKETS.length).fill(0) as number[],\n sum: 0,\n count: 0,\n };\n histogram.series.set(key, existing);\n }\n // Update cumulative bucket counts\n for (let i = 0; i < HISTOGRAM_BUCKETS.length; i++) {\n if (value <= HISTOGRAM_BUCKETS[i]) {\n existing.bucketCounts[i] += 1;\n }\n }\n existing.sum += value;\n existing.count += 1;\n },\n\n setGauge(name: string, labels: Record<string, string>, value: number): void {\n const gauge = getOrCreateGauge(name);\n const key = labelKey(labels);\n const existing = gauge.series.get(key);\n if (existing) {\n existing.value = value;\n } else {\n gauge.series.set(key, { labels, value });\n }\n },\n\n serialize(): string {\n const lines: string[] = [];\n\n for (const [name, data] of metrics) {\n switch (data.type) {\n case \"counter\": {\n lines.push(`# TYPE ${name} counter`);\n for (const series of data.series.values()) {\n lines.push(`${name}${formatLabels(series.labels)} ${series.value}`);\n }\n break;\n }\n case \"histogram\": {\n lines.push(`# TYPE ${name} histogram`);\n for (const series of data.series.values()) {\n const lblStr = labelKey(series.labels);\n const lblPrefix = lblStr ? `${lblStr},` : \"\";\n // Bucket lines\n for (let i = 0; i < HISTOGRAM_BUCKETS.length; i++) {\n lines.push(\n `${name}_bucket{${lblPrefix}le=\"${HISTOGRAM_BUCKETS[i]}\"} ${series.bucketCounts[i]}`,\n );\n }\n // +Inf bucket\n lines.push(`${name}_bucket{${lblPrefix}le=\"+Inf\"} ${series.count}`);\n // Sum and count\n lines.push(`${name}_sum${formatLabels(series.labels)} ${series.sum}`);\n lines.push(`${name}_count${formatLabels(series.labels)} ${series.count}`);\n }\n break;\n }\n case \"gauge\": {\n lines.push(`# TYPE ${name} gauge`);\n for (const series of data.series.values()) {\n lines.push(`${name}${formatLabels(series.labels)} ${series.value}`);\n }\n break;\n }\n }\n }\n\n return lines.length > 0 ? lines.join(\"\\n\") + \"\\n\" : \"\";\n },\n\n reset(): void {\n metrics.clear();\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Path normalization for metric labels\n// ---------------------------------------------------------------------------\n\n// Regex patterns for parametric API routes\nconst BEDROCK_RE =\n /^\\/model\\/([^/]+)\\/(invoke|invoke-with-response-stream|converse|converse-stream)$/;\n// Gemini `/v1beta/models/{model}:{action}`. The action segment is caller\n// controlled, so only the actions the server routes stay verbatim; anything\n// else collapses to `{action}`. `predictLongRunning` is listed so the Veo\n// submit label below stays byte-identical to what it was before this RE\n// widened past generate/streamGenerate — the two rules must agree.\nconst GEMINI_RE = /^\\/v1beta\\/models\\/([^:]+):([^/]+)$/;\nconst GEMINI_ACTIONS = new Set([\n \"generateContent\",\n \"streamGenerateContent\",\n \"embedContent\",\n \"batchEmbedContents\",\n \"countTokens\",\n \"predict\",\n \"predictLongRunning\",\n]);\nconst GEMINI_MODEL_RE = /^\\/v1beta\\/models\\/([^:/]+)$/;\nconst AZURE_RE = /^\\/openai\\/deployments\\/([^/]+)\\/(chat\\/completions|embeddings)$/;\nconst ELEVENLABS_TTS_RE = /^\\/v1\\/text-to-speech\\/([^/]+)$/;\nconst ELEVENLABS_VOICE_RE = /^\\/v1\\/voices\\/([^/]+)$/;\nconst VERTEX_RE =\n /^\\/v1\\/projects\\/([^/]+)\\/locations\\/([^/]+)\\/publishers\\/google\\/models\\/([^:]+):(.+)$/;\n// The Vertex `:action` segment is caller controlled exactly as Gemini's is;\n// server.ts routes only these two, so anything else collapses to `{action}`.\nconst VERTEX_ACTIONS = new Set([\"generateContent\", \"streamGenerateContent\"]);\n// Exported: server.ts route dispatch matches the same OpenRouter and OpenAI\n// video paths.\nexport const OPENROUTER_VIDEO_CONTENT_RE = /^\\/api\\/v1\\/videos\\/([^/]+)\\/content$/;\nexport const OPENROUTER_VIDEO_STATUS_RE = /^\\/api\\/v1\\/videos\\/([^/]+)$/;\nexport const OPENAI_VIDEO_STATUS_RE = /^\\/v1\\/videos\\/([^/]+)$/;\n// Exported: server.ts route dispatch matches the same Google Veo and xAI Grok\n// video paths. Veo submit (`:predictLongRunning`) is anchored so it never\n// collides with the bare Gemini `:predict` route; Veo operations live in a\n// fresh `/v1beta/operations/...` namespace. Grok submit is an exact literal\n// path; Grok status reuses the OpenAI `/v1/videos/{id}` shape (the dispatch\n// guards `id !== \"generations\"` and the job-map lookup disambiguates Sora).\nexport const VEO_PREDICT_LRO_RE = /^\\/v1beta\\/models\\/([^:]+):predictLongRunning$/;\nexport const VEO_OPERATION_RE = /^\\/v1beta\\/(operations\\/.+)$/;\nexport const GROK_VIDEO_SUBMIT_PATH = \"/v1/videos/generations\";\nexport const GROK_VIDEO_STATUS_RE = /^\\/v1\\/videos\\/([^/]+)$/;\n\n/**\n * BytePlus Ark (Seedance) async video task routes. The `/api/v3` prefix is\n * ENUMERATED rather than wildcarded: Ark's data-plane base carries it, but a\n * client may point `baseURL` at a bare aimock root instead, so both forms must\n * route. A tolerant `(?:\\/[^?]*)?` prefix would additionally claim\n * `/fal/contents/generations/tasks` — and these routes dispatch in the\n * pre-rewrite band, ~1,100 lines ahead of every fal branch, so it would take\n * that path away from the fal proxy. Enumerating the one real prefix also\n * blocks a doubled suffix from matching the status RE.\n *\n * Declared here (not in server.ts) because normalizePathLabel below consumes\n * them, matching the existing OpenRouter/Veo/Grok route-regex edge where\n * server.ts imports its route regexes from this module.\n */\nexport const BYTEPLUS_VIDEO_SUBMIT_RE = /^(?:\\/api\\/v3)?\\/contents\\/generations\\/tasks$/;\nexport const BYTEPLUS_VIDEO_STATUS_RE = /^(?:\\/api\\/v3)?\\/contents\\/generations\\/tasks\\/([^/]+)$/;\nexport const BATCHES_CANCEL_RE = /^\\/v1\\/batches\\/([^/]+)\\/cancel$/;\nexport const BATCHES_ID_RE = /^\\/v1\\/batches\\/([^/]+)$/;\nexport const FILES_CONTENT_RE = /^\\/v1\\/files\\/([^/]+)\\/content$/;\nexport const FILES_ID_RE = /^\\/v1\\/files\\/([^/]+)$/;\n\n/**\n * Closed namespaces beyond fine-tuning. Each prefix below is entered by prefix\n * and always returns, so no id-bearing or unknown-depth path under it can\n * reach the verbatim return at the bottom of `normalizePathLabel`:\n *\n * - fal: `/fal/queue/requests/{id}[/status|/cancel|/stream]` (aimock's own\n * queue layout), `/fal/{model}/requests/{id}[/…]` (the `x-fal-target-host`\n * path-mirror of `queue.fal.run`), `/fal/queue/submit/{model}`,\n * `/fal/run/{model}`. Request ids are minted per submit and model ids are\n * caller-controlled multi-segment paths, so both collapse.\n * - music: `/v1/music/{generation|variation|remix|extend|plan|detailed|stream}`\n * are the ElevenLabs routes (`plan`/`stream` take their own response shape\n * in elevenlabs-audio.ts); the server RE accepts any suffix, so an unknown\n * one collapses.\n * - files / batches: the id and sub-resource rules above cover the routed\n * shapes; any other depth collapses to the namespace's `{other}`.\n */\n/**\n * The fal.ai route shape, shared with server.ts so the label rule cannot\n * drift from the route rule: a bare `/fal` is routed, so it is labelled here\n * rather than falling through to `{unknown}`.\n */\nexport const FAL_ROUTE_RE = /^\\/fal(?:\\/.*)?$/;\nconst FAL_QUEUE_REQUEST_RE = /^\\/fal\\/queue\\/requests\\/[^/]+(?:\\/([^/]+))?$/;\nconst FAL_MIRROR_REQUEST_RE = /^\\/fal\\/.+\\/requests\\/[^/]+(?:\\/([^/]+))?$/;\nconst FAL_REQUEST_SUBRESOURCES = new Set([\"status\", \"cancel\", \"stream\"]);\nconst FAL_QUEUE_SUBMIT_RE = /^\\/fal\\/queue\\/submit\\/.+$/;\nconst FAL_RUN_RE = /^\\/fal\\/run\\/.+$/;\nconst FAL_OTHER_LABEL = \"/fal/{other}\";\nconst MUSIC_PREFIX = \"/v1/music/\";\nconst MUSIC_ACTIONS = new Set([\n \"generation\",\n \"variation\",\n \"remix\",\n \"extend\",\n \"plan\",\n \"detailed\",\n \"stream\",\n]);\nconst MUSIC_OTHER_LABEL = \"/v1/music/{other}\";\nconst FILES_PREFIX = \"/v1/files/\";\nconst FILES_OTHER_LABEL = \"/v1/files/{other}\";\nconst BATCHES_PREFIX = \"/v1/batches/\";\nconst BATCHES_OTHER_LABEL = \"/v1/batches/{other}\";\n\n/**\n * Label for a path no route claimed. Metrics are recorded for EVERY response,\n * the generic 404 included, so an unrouted path is caller-controlled text and\n * a fuzzer (or a typo'd SDK base URL) would mint one label per request.\n *\n * The collapse is decided by ROUTE SHAPE, never by status: a 404 is not the\n * only way an unrouted path gets answered (a CORS preflight is 204 for any\n * path, a torn-down response has no status at all), and a routed path can\n * legitimately 404 (a fixture miss, a missing file) without becoming\n * unknown. Every path that is not claimed by a placeholder rule above must\n * therefore be listed here to keep its verbatim label.\n */\nexport const UNKNOWN_PATH_LABEL = \"{unknown}\";\n\n/**\n * Static paths server.ts dispatches by exact match (after\n * `normalizeCompatPath`, so the canonical `/v1/...` spelling). A path absent\n * from this table that no placeholder rule claims is labelled `{unknown}`\n * whatever its status — so a new static route needs an entry here or its\n * traffic is counted but not named.\n */\nconst STATIC_ROUTE_PATHS = new Set([\n \"/health\",\n \"/ready\",\n \"/metrics\",\n \"/search\",\n \"/v1/chat/completions\",\n \"/v1/responses\",\n \"/v1/realtime\",\n \"/v1/messages\",\n \"/v1/embeddings\",\n \"/v1/moderations\",\n \"/v1/models\",\n \"/v1/_requests\",\n \"/v1/images/generations\",\n \"/v1/images/edits\",\n \"/v1/images/variations\",\n \"/v1/audio/speech\",\n \"/v1/audio/transcriptions\",\n \"/v1/audio/translations\",\n \"/v1/videos\",\n \"/v1/batches\",\n \"/v1/files\",\n \"/v1/sound-generation\",\n \"/v1/music\",\n \"/v1/text-to-voice\",\n \"/v1/text-to-voice/design\",\n \"/v1beta/interactions\",\n \"/v2/chat\",\n \"/v2/embed\",\n \"/v2/rerank\",\n \"/api/chat\",\n \"/api/generate\",\n \"/api/embeddings\",\n \"/api/embed\",\n \"/api/tags\",\n \"/api/v1/videos\",\n \"/api/v1/videos/models\",\n \"/api/v1/credits\",\n \"/api/v1/key\",\n \"/api/v1/models\",\n \"/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent\",\n]);\n\n/**\n * Control API (`/__aimock/...`): a closed set of sub-paths, matched exactly\n * in `handleControlAPI`; anything else under the prefix collapses.\n */\nconst CONTROL_PREFIX = \"/__aimock/\";\nconst CONTROL_PATHS = new Set([\n \"/__aimock/health\",\n \"/__aimock/journal\",\n \"/__aimock/fixtures\",\n \"/__aimock/chaos\",\n \"/__aimock/reset\",\n \"/__aimock/reset/journal\",\n \"/__aimock/reset/fixtures\",\n \"/__aimock/error\",\n]);\nconst CONTROL_OTHER_LABEL = \"/__aimock/{other}\";\n\n/**\n * Sub-paths a mounted service (`createServer(..., mounts)`) answers below its\n * mount root. The mount roots themselves are runtime configuration, so the\n * caller passes them in; the JSON-RPC handlers (MCP, A2A, AG-UI, vector) take\n * the root only, and A2A additionally serves its agent card.\n */\nconst MOUNT_SUBPATHS = new Set([\"/.well-known/agent-card.json\"]);\n\n/**\n * Status label for a response that was destroyed before its body ended: no\n * status line reached the client as a completed response, so no HTTP code\n * describes the outcome truthfully. Distinct from every numeric status so\n * `sum(aimock_requests_total)` equals requests served.\n */\nexport const DESTROYED_STATUS_LABEL = \"destroyed\";\n\n/**\n * Fine-tuning routes. Both id families (`ftjob-…` per create, `ftckpt-…` per\n * checkpoint) are minted per resource, so every id-bearing path has to collapse\n * or each job and each checkpoint mints its own label pair.\n *\n * The label hazard is NOT limited to the routes this server implements: metrics\n * are recorded on `res.on(\"finish\")` for EVERY response, the generic 404\n * included, and `openai@4.104.0` calls a good deal more of the namespace than\n * aimock handles. Enumerated from that SDK:\n *\n * - `resources/fine-tuning/jobs/jobs.js` — `/fine_tuning/jobs`,\n * `…/jobs/{id}`, `…/jobs/{id}/cancel`, `…/jobs/{id}/pause`,\n * `…/jobs/{id}/resume`\n * - `resources/fine-tuning/jobs/checkpoints.js` — `…/jobs/{id}/checkpoints`\n * - `resources/fine-tuning/checkpoints/permissions.js` —\n * `…/checkpoints/{ckpt}/permissions` (create/list) and\n * `…/checkpoints/{ckpt}/permissions/{id}` (delete)\n * - `resources/fine-tuning/alpha/graders.js` —\n * `/fine_tuning/alpha/graders/run`, `/fine_tuning/alpha/graders/validate`\n *\n * The grader routes carry no ids, so they need no placeholder — but they DO\n * need naming here, because the namespace cascade below ends in a catch-all\n * and a static route must not be swallowed by it.\n *\n * Every segment after `/v1/fine_tuning/` is caller-controlled, so the rule is:\n * known action names are kept verbatim, ids collapse to `{id}`/`{ckpt}`, and\n * anything else — an unknown action, an unknown depth — collapses to a single\n * bucket. That is what actually keeps the fine-tuning label set finite no\n * matter what is requested; an un-collapsed tail would leave the hole open to\n * a typo or a fuzzer.\n *\n * Not exported: server.ts routes fine-tuning with its own private REs.\n */\nconst FINE_TUNING_PREFIX = \"/v1/fine_tuning/\";\nconst FINE_TUNING_STATIC_PATHS = new Set([\n \"/v1/fine_tuning/jobs\",\n \"/v1/fine_tuning/alpha/graders/run\",\n \"/v1/fine_tuning/alpha/graders/validate\",\n]);\nconst FINE_TUNING_SUBRESOURCE_RE = /^\\/v1\\/fine_tuning\\/jobs\\/[^/]+\\/([^/]+)$/;\nconst FINE_TUNING_SUBRESOURCES = new Set([\"cancel\", \"events\", \"pause\", \"resume\", \"checkpoints\"]);\nconst FINE_TUNING_ID_RE = /^\\/v1\\/fine_tuning\\/jobs\\/([^/]+)$/;\nconst FINE_TUNING_PERMISSION_ID_RE = /^\\/v1\\/fine_tuning\\/checkpoints\\/[^/]+\\/permissions\\/[^/]+$/;\nconst FINE_TUNING_CHECKPOINT_SUBRESOURCE_RE = /^\\/v1\\/fine_tuning\\/checkpoints\\/[^/]+\\/([^/]+)$/;\nconst FINE_TUNING_CHECKPOINT_SUBRESOURCES = new Set([\"permissions\"]);\nconst FINE_TUNING_CHECKPOINT_ID_RE = /^\\/v1\\/fine_tuning\\/checkpoints\\/([^/]+)$/;\nconst FINE_TUNING_OTHER_LABEL = \"/v1/fine_tuning/{other}\";\n\n/**\n * Normalize parametric API paths to route patterns for use as metric labels.\n * Replaces dynamic segments (model IDs, deployment names, etc.) with placeholders.\n * `mountPaths` are the roots of the mounted services, so their traffic keeps a\n * bounded label instead of collapsing to `{unknown}`.\n */\nexport function normalizePathLabel(pathname: string, mountPaths: readonly string[] = []): string {\n // Match server dispatch precedence: controls, mounts in registration order,\n // then provider routes. A mount may overlap any provider namespace.\n // Control API: exact sub-paths verbatim, anything else under the prefix\n // collapses.\n if (pathname.startsWith(CONTROL_PREFIX)) {\n return CONTROL_PATHS.has(pathname) ? pathname : CONTROL_OTHER_LABEL;\n }\n\n // Mounted services: the root and the known sub-paths verbatim, anything\n // deeper collapses under the mount.\n for (const mountPath of mountPaths) {\n if (pathname === mountPath) return pathname;\n if (pathname.startsWith(mountPath + \"/\")) {\n return MOUNT_SUBPATHS.has(pathname.slice(mountPath.length))\n ? pathname\n : `${mountPath}/{other}`;\n }\n }\n\n // Bedrock: /model/{modelId}/{operation}\n const bedrockMatch = pathname.match(BEDROCK_RE);\n if (bedrockMatch) {\n return `/model/{modelId}/${bedrockMatch[2]}`;\n }\n\n // Gemini: /v1beta/models/{model}:{action}, plus the bare GET model lookup.\n const geminiMatch = pathname.match(GEMINI_RE);\n if (geminiMatch) {\n const action = GEMINI_ACTIONS.has(geminiMatch[2]) ? geminiMatch[2] : \"{action}\";\n return `/v1beta/models/{model}:${action}`;\n }\n if (GEMINI_MODEL_RE.test(pathname)) {\n return \"/v1beta/models/{model}\";\n }\n\n // Azure: /openai/deployments/{id}/{operation}\n const azureMatch = pathname.match(AZURE_RE);\n if (azureMatch) {\n return `/openai/deployments/{id}/${azureMatch[2]}`;\n }\n\n // Vertex AI: /v1/projects/{p}/locations/{l}/publishers/google/models/{m}:{action}\n const vertexMatch = pathname.match(VERTEX_RE);\n if (vertexMatch) {\n const action = VERTEX_ACTIONS.has(vertexMatch[4]) ? vertexMatch[4] : \"{action}\";\n return `/v1/projects/{p}/locations/{l}/publishers/google/models/{m}:${action}`;\n }\n\n // ElevenLabs TTS: /v1/text-to-speech/{voice_id}\n if (ELEVENLABS_TTS_RE.test(pathname)) {\n return \"/v1/text-to-speech/{voice_id}\";\n }\n\n // ElevenLabs voices: /v1/voices/{voice_id}\n if (ELEVENLABS_VOICE_RE.test(pathname)) {\n return \"/v1/voices/{voice_id}\";\n }\n\n // OpenRouter video: /api/v1/videos/{jobId}[/content] — jobIds are random\n // UUIDs, so raw paths would mint unbounded label cardinality. The static\n // /api/v1/videos/models listing route must not collapse into {jobId}.\n if (OPENROUTER_VIDEO_CONTENT_RE.test(pathname)) {\n return \"/api/v1/videos/{jobId}/content\";\n }\n if (pathname !== \"/api/v1/videos/models\" && OPENROUTER_VIDEO_STATUS_RE.test(pathname)) {\n return \"/api/v1/videos/{jobId}\";\n }\n\n // Google Veo video: submit `:predictLongRunning` + poll `/v1beta/operations/{name}`.\n // Operation names are random UUIDs, so the raw path would mint unbounded\n // label cardinality.\n if (VEO_PREDICT_LRO_RE.test(pathname)) {\n return \"/v1beta/models/{model}:predictLongRunning\";\n }\n if (VEO_OPERATION_RE.test(pathname)) {\n return \"/v1beta/operations/{name}\";\n }\n\n // xAI Grok Imagine submit is a static literal path — keep it distinct from\n // the `/v1/videos/{id}` status label below (which it would otherwise collapse\n // into), exactly as `/api/v1/videos/models` is kept out of the jobId bucket.\n if (pathname === GROK_VIDEO_SUBMIT_PATH) {\n return GROK_VIDEO_SUBMIT_PATH;\n }\n\n // OpenAI/Grok video status: /v1/videos/{id}\n if (OPENAI_VIDEO_STATUS_RE.test(pathname)) {\n return \"/v1/videos/{id}\";\n }\n\n // BytePlus Ark video: /[api/v3/]contents/generations/tasks[/{id}] — task ids\n // (`cgt-…`) are unbounded cardinality. Status before submit: the status RE is\n // the more specific of the two and the submit RE cannot match a trailing id\n // segment, so the order is belt-and-braces. Appended at the end of the\n // cascade because the anchored REs above cannot match any path an earlier\n // entry claims.\n if (BYTEPLUS_VIDEO_STATUS_RE.test(pathname)) {\n return \"/contents/generations/tasks/{id}\";\n }\n if (BYTEPLUS_VIDEO_SUBMIT_RE.test(pathname)) {\n return \"/contents/generations/tasks\";\n }\n\n if (BATCHES_CANCEL_RE.test(pathname)) {\n return \"/v1/batches/{id}/cancel\";\n }\n if (pathname !== \"/v1/batches\" && BATCHES_ID_RE.test(pathname)) {\n return \"/v1/batches/{id}\";\n }\n if (pathname.startsWith(BATCHES_PREFIX)) {\n return BATCHES_OTHER_LABEL;\n }\n\n // Files API: /v1/files/{id} and /v1/files/{id}/content carry random\n // `file-…` ids — raw paths would mint unbounded label cardinality.\n // Content before id: the id RE would otherwise swallow the content suffix.\n if (FILES_CONTENT_RE.test(pathname)) {\n return \"/v1/files/{id}/content\";\n }\n if (pathname !== \"/v1/files\" && FILES_ID_RE.test(pathname)) {\n return \"/v1/files/{id}\";\n }\n if (pathname.startsWith(FILES_PREFIX)) {\n return FILES_OTHER_LABEL;\n }\n\n // fal.ai: see the namespace note above. Queue-request rules read before the\n // path-mirror rule because `/fal/queue/requests/…` also matches the mirror\n // RE's `.+/requests/` shape; both would label correctly, but the specific\n // rule reading first keeps the cascade legible.\n if (FAL_ROUTE_RE.test(pathname)) {\n const queueRequest = pathname.match(FAL_QUEUE_REQUEST_RE);\n if (queueRequest) {\n return `/fal/queue/requests/{id}${falRequestSuffix(queueRequest[1])}`;\n }\n if (FAL_QUEUE_SUBMIT_RE.test(pathname)) return \"/fal/queue/submit/{model}\";\n if (FAL_RUN_RE.test(pathname)) return \"/fal/run/{model}\";\n const mirrorRequest = pathname.match(FAL_MIRROR_REQUEST_RE);\n if (mirrorRequest) {\n return `/fal/{model}/requests/{id}${falRequestSuffix(mirrorRequest[1])}`;\n }\n return FAL_OTHER_LABEL;\n }\n\n // ElevenLabs Music: known actions verbatim, anything else collapses.\n if (pathname.startsWith(MUSIC_PREFIX)) {\n return MUSIC_ACTIONS.has(pathname.slice(MUSIC_PREFIX.length)) ? pathname : MUSIC_OTHER_LABEL;\n }\n\n // Fine-tuning. Handled as one closed namespace rather than a few loose REs:\n // the cascade is entered by prefix and always returns, so no fine-tuning path\n // can reach the verbatim return at the bottom of this function.\n //\n // Order matters twice. The id-bearing permission rule reads before the\n // checkpoint sub-resource rule, because `…/permissions/{id}` would otherwise\n // never be reached (its own second segment is the id). Within each family the\n // sub-resource rule reads before the id rule; the id REs are anchored to a\n // single trailing segment so the two cannot both match, but the more specific\n // path reading first is what makes the cascade legible.\n if (pathname.startsWith(FINE_TUNING_PREFIX)) {\n if (FINE_TUNING_STATIC_PATHS.has(pathname)) return pathname;\n\n const ftSubresource = pathname.match(FINE_TUNING_SUBRESOURCE_RE);\n if (ftSubresource) {\n const action = FINE_TUNING_SUBRESOURCES.has(ftSubresource[1]) ? ftSubresource[1] : \"{action}\";\n return `/v1/fine_tuning/jobs/{id}/${action}`;\n }\n if (FINE_TUNING_ID_RE.test(pathname)) return \"/v1/fine_tuning/jobs/{id}\";\n\n if (FINE_TUNING_PERMISSION_ID_RE.test(pathname)) {\n return \"/v1/fine_tuning/checkpoints/{ckpt}/permissions/{id}\";\n }\n const ckptSubresource = pathname.match(FINE_TUNING_CHECKPOINT_SUBRESOURCE_RE);\n if (ckptSubresource) {\n const action = FINE_TUNING_CHECKPOINT_SUBRESOURCES.has(ckptSubresource[1])\n ? ckptSubresource[1]\n : \"{action}\";\n return `/v1/fine_tuning/checkpoints/{ckpt}/${action}`;\n }\n if (FINE_TUNING_CHECKPOINT_ID_RE.test(pathname)) {\n return \"/v1/fine_tuning/checkpoints/{ckpt}\";\n }\n\n return FINE_TUNING_OTHER_LABEL;\n }\n\n // Static path — verbatim only if server.ts routes it. Anything else is, as\n // far as metrics can tell, caller-controlled text, whatever status it got\n // (404, a 204 preflight, a destroyed response); every routed id path that\n // can legitimately 404 has already returned its placeholder label above.\n return STATIC_ROUTE_PATHS.has(pathname) ? pathname : UNKNOWN_PATH_LABEL;\n}\n\nfunction falRequestSuffix(sub: string | undefined): string {\n if (sub === undefined) return \"\";\n return FAL_REQUEST_SUBRESOURCES.has(sub) ? `/${sub}` : \"/{other}\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"metrics.js","names":["AZURE_RE"],"sources":["../src/metrics.ts"],"sourcesContent":["/**\n * Lightweight Prometheus metrics registry for LLMock.\n *\n * Zero external dependencies — implements counters, histograms, and gauges\n * with Prometheus text exposition format serialization.\n */\n\nimport {\n AZURE_DEPLOYMENT_RE as AZURE_RE,\n BATCHES_CANCEL_RE,\n BATCHES_ID_RE,\n BYTEPLUS_VIDEO_STATUS_RE,\n BYTEPLUS_VIDEO_SUBMIT_RE,\n ELEVENLABS_TTS_RE,\n ELEVENLABS_VOICE_RE,\n FAL_ROUTE_RE,\n FILES_CONTENT_RE,\n FILES_ID_RE,\n FINE_TUNING_ID_RE,\n GROK_VIDEO_STATUS_RE,\n GROK_VIDEO_SUBMIT_PATH,\n OPENAI_VIDEO_STATUS_RE,\n OPENROUTER_VIDEO_CONTENT_RE,\n OPENROUTER_VIDEO_STATUS_RE,\n VEO_OPERATION_RE,\n VEO_PREDICT_LRO_RE,\n VECTOR_STORES_BATCH_CANCEL_RE,\n VECTOR_STORES_BATCH_FILES_RE,\n VECTOR_STORES_BATCH_RE,\n VECTOR_STORES_FILES_RE,\n VECTOR_STORES_FILE_BATCHES_RE,\n VECTOR_STORES_FILE_RE,\n VECTOR_STORES_ID_RE,\n VECTOR_STORES_SEARCH_RE,\n} from \"./route-registry.js\";\n\n// Re-exported so existing importers (route tests) keep resolving these\n// against the metrics module. The bindings ARE the registry's — there is\n// exactly one pattern object per route family.\nexport {\n BATCHES_CANCEL_RE,\n BATCHES_ID_RE,\n BYTEPLUS_VIDEO_STATUS_RE,\n BYTEPLUS_VIDEO_SUBMIT_RE,\n FAL_ROUTE_RE,\n FILES_CONTENT_RE,\n FILES_ID_RE,\n GROK_VIDEO_STATUS_RE,\n GROK_VIDEO_SUBMIT_PATH,\n OPENAI_VIDEO_STATUS_RE,\n OPENROUTER_VIDEO_CONTENT_RE,\n OPENROUTER_VIDEO_STATUS_RE,\n VEO_OPERATION_RE,\n VEO_PREDICT_LRO_RE,\n VECTOR_STORES_BATCH_CANCEL_RE,\n VECTOR_STORES_BATCH_FILES_RE,\n VECTOR_STORES_BATCH_RE,\n VECTOR_STORES_FILE_RE,\n VECTOR_STORES_ID_RE,\n VECTOR_STORES_SEARCH_RE,\n};\n\n// ---------------------------------------------------------------------------\n// Public interface\n// ---------------------------------------------------------------------------\n\nexport interface MetricsRegistry {\n incrementCounter(name: string, labels: Record<string, string>): void;\n observeHistogram(name: string, labels: Record<string, string>, value: number): void;\n setGauge(name: string, labels: Record<string, string>, value: number): void;\n serialize(): string;\n reset(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Histogram bucket boundaries (Prometheus default-ish)\n// ---------------------------------------------------------------------------\n\nconst HISTOGRAM_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Build a stable label key string for map lookups: `label1=\"v1\",label2=\"v2\"` */\nfunction labelKey(labels: Record<string, string>): string {\n const entries = Object.entries(labels).sort(([a], [b]) => a.localeCompare(b));\n if (entries.length === 0) return \"\";\n return entries.map(([k, v]) => `${k}=\"${escapeLabelValue(v)}\"`).join(\",\");\n}\n\n/** Escape a label value per Prometheus text exposition format. */\nfunction escapeLabelValue(v: string): string {\n return v.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/\\n/g, \"\\\\n\");\n}\n\n/** Format labels for Prometheus output: `{label1=\"v1\",label2=\"v2\"}` */\nfunction formatLabels(labels: Record<string, string>): string {\n return `{${labelKey(labels)}}`;\n}\n\n// ---------------------------------------------------------------------------\n// Internal metric storage types\n// ---------------------------------------------------------------------------\n\ninterface CounterData {\n type: \"counter\";\n /** Map from labelKey → value */\n series: Map<string, { labels: Record<string, string>; value: number }>;\n}\n\ninterface HistogramData {\n type: \"histogram\";\n /** Map from labelKey → bucket counts, sum, count */\n series: Map<\n string,\n {\n labels: Record<string, string>;\n bucketCounts: number[]; // one per HISTOGRAM_BUCKETS entry\n sum: number;\n count: number;\n }\n >;\n}\n\ninterface GaugeData {\n type: \"gauge\";\n /** Map from labelKey → value */\n series: Map<string, { labels: Record<string, string>; value: number }>;\n}\n\ntype MetricData = CounterData | HistogramData | GaugeData;\n\n// ---------------------------------------------------------------------------\n// Registry implementation\n// ---------------------------------------------------------------------------\n\nexport function createMetricsRegistry(): MetricsRegistry {\n /** Ordered map: metric name → data. Insertion order preserved for stable output. */\n const metrics = new Map<string, MetricData>();\n\n function getOrCreateCounter(name: string): CounterData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"counter\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"counter\") throw new Error(`Metric ${name} is not a counter`);\n return data as CounterData;\n }\n\n function getOrCreateHistogram(name: string): HistogramData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"histogram\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"histogram\") throw new Error(`Metric ${name} is not a histogram`);\n return data as HistogramData;\n }\n\n function getOrCreateGauge(name: string): GaugeData {\n let data = metrics.get(name);\n if (!data) {\n data = { type: \"gauge\", series: new Map() };\n metrics.set(name, data);\n }\n if (data.type !== \"gauge\") throw new Error(`Metric ${name} is not a gauge`);\n return data as GaugeData;\n }\n\n return {\n incrementCounter(name: string, labels: Record<string, string>): void {\n const counter = getOrCreateCounter(name);\n const key = labelKey(labels);\n const existing = counter.series.get(key);\n if (existing) {\n existing.value += 1;\n } else {\n counter.series.set(key, { labels, value: 1 });\n }\n },\n\n observeHistogram(name: string, labels: Record<string, string>, value: number): void {\n const histogram = getOrCreateHistogram(name);\n const key = labelKey(labels);\n let existing = histogram.series.get(key);\n if (!existing) {\n existing = {\n labels,\n bucketCounts: new Array(HISTOGRAM_BUCKETS.length).fill(0) as number[],\n sum: 0,\n count: 0,\n };\n histogram.series.set(key, existing);\n }\n // Update cumulative bucket counts\n for (let i = 0; i < HISTOGRAM_BUCKETS.length; i++) {\n if (value <= HISTOGRAM_BUCKETS[i]) {\n existing.bucketCounts[i] += 1;\n }\n }\n existing.sum += value;\n existing.count += 1;\n },\n\n setGauge(name: string, labels: Record<string, string>, value: number): void {\n const gauge = getOrCreateGauge(name);\n const key = labelKey(labels);\n const existing = gauge.series.get(key);\n if (existing) {\n existing.value = value;\n } else {\n gauge.series.set(key, { labels, value });\n }\n },\n\n serialize(): string {\n const lines: string[] = [];\n\n for (const [name, data] of metrics) {\n switch (data.type) {\n case \"counter\": {\n lines.push(`# TYPE ${name} counter`);\n for (const series of data.series.values()) {\n lines.push(`${name}${formatLabels(series.labels)} ${series.value}`);\n }\n break;\n }\n case \"histogram\": {\n lines.push(`# TYPE ${name} histogram`);\n for (const series of data.series.values()) {\n const lblStr = labelKey(series.labels);\n const lblPrefix = lblStr ? `${lblStr},` : \"\";\n // Bucket lines\n for (let i = 0; i < HISTOGRAM_BUCKETS.length; i++) {\n lines.push(\n `${name}_bucket{${lblPrefix}le=\"${HISTOGRAM_BUCKETS[i]}\"} ${series.bucketCounts[i]}`,\n );\n }\n // +Inf bucket\n lines.push(`${name}_bucket{${lblPrefix}le=\"+Inf\"} ${series.count}`);\n // Sum and count\n lines.push(`${name}_sum${formatLabels(series.labels)} ${series.sum}`);\n lines.push(`${name}_count${formatLabels(series.labels)} ${series.count}`);\n }\n break;\n }\n case \"gauge\": {\n lines.push(`# TYPE ${name} gauge`);\n for (const series of data.series.values()) {\n lines.push(`${name}${formatLabels(series.labels)} ${series.value}`);\n }\n break;\n }\n }\n }\n\n return lines.length > 0 ? lines.join(\"\\n\") + \"\\n\" : \"\";\n },\n\n reset(): void {\n metrics.clear();\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Path normalization for metric labels\n// ---------------------------------------------------------------------------\n\n// Regex patterns for parametric API routes. The dispatch+labeling patterns\n// shared with server.ts dispatch and the machine-readable catalog are defined\n// in route-registry.ts (single source of truth) and imported above; only\n// labeling-specific patterns are declared here: the Bedrock union, the Looser\n// Vertex shape, the widened Gemini action bucket (+ its allowlists), and the\n// served-or-not namespace cascades below.\nconst BEDROCK_RE =\n /^\\/model\\/([^/]+)\\/(invoke|invoke-with-response-stream|converse|converse-stream)$/;\nconst VERTEX_RE =\n /^\\/v1\\/projects\\/([^/]+)\\/locations\\/([^/]+)\\/publishers\\/google\\/models\\/([^:]+):(.+)$/;\n// Gemini `/v1beta/models/{model}:{action}`. The action segment is caller\n// controlled, so only the actions the server routes stay verbatim; anything\n// else collapses to `{action}`. `predictLongRunning` is listed so the Veo\n// submit label below stays byte-identical to what it was before this RE\n// widened past generate/streamGenerate — the two rules must agree.\nconst GEMINI_RE = /^\\/v1beta\\/models\\/([^:]+):([^/]+)$/;\nconst GEMINI_ACTIONS = new Set([\n \"generateContent\",\n \"streamGenerateContent\",\n \"embedContent\",\n \"batchEmbedContents\",\n \"countTokens\",\n \"predict\",\n \"predictLongRunning\",\n]);\nconst GEMINI_MODEL_RE = /^\\/v1beta\\/models\\/([^:/]+)$/;\n// The Vertex `:action` segment is caller controlled exactly as Gemini's is;\n// server.ts routes only these two, so anything else collapses to `{action}`.\nconst VERTEX_ACTIONS = new Set([\"generateContent\", \"streamGenerateContent\"]);\n\n/**\n * Closed namespaces beyond fine-tuning. Each prefix below is entered by prefix\n * and always returns, so no id-bearing or unknown-depth path under it can\n * reach the verbatim return at the bottom of `normalizePathLabel`:\n *\n * - fal: `/fal/queue/requests/{id}[/status|/cancel|/stream]` (aimock's own\n * queue layout), `/fal/{model}/requests/{id}[/…]` (the `x-fal-target-host`\n * path-mirror of `queue.fal.run`), `/fal/queue/submit/{model}`,\n * `/fal/run/{model}`. Request ids are minted per submit and model ids are\n * caller-controlled multi-segment paths, so both collapse.\n * - music: `/v1/music/{generation|variation|remix|extend|plan|detailed|stream}`\n * are the ElevenLabs routes (`plan`/`stream` take their own response shape\n * in elevenlabs-audio.ts); the server RE accepts any suffix, so an unknown\n * one collapses.\n * - files / batches: the id and sub-resource rules above cover the routed\n * shapes; any other depth collapses to the namespace's `{other}`.\n */\n/**\n * The fal.ai route shape (`FAL_ROUTE_RE`, imported from route-registry.ts so\n * the label rule cannot drift from the route rule): a bare `/fal` is routed,\n * so it is labelled here rather than falling through to `{unknown}`.\n */\nconst FAL_QUEUE_REQUEST_RE = /^\\/fal\\/queue\\/requests\\/[^/]+(?:\\/([^/]+))?$/;\nconst FAL_MIRROR_REQUEST_RE = /^\\/fal\\/.+\\/requests\\/[^/]+(?:\\/([^/]+))?$/;\nconst FAL_REQUEST_SUBRESOURCES = new Set([\"status\", \"cancel\", \"stream\"]);\nconst FAL_QUEUE_SUBMIT_RE = /^\\/fal\\/queue\\/submit\\/.+$/;\nconst FAL_RUN_RE = /^\\/fal\\/run\\/.+$/;\nconst FAL_OTHER_LABEL = \"/fal/{other}\";\nconst MUSIC_PREFIX = \"/v1/music/\";\nconst MUSIC_ACTIONS = new Set([\n \"generation\",\n \"variation\",\n \"remix\",\n \"extend\",\n \"plan\",\n \"detailed\",\n \"stream\",\n]);\nconst MUSIC_OTHER_LABEL = \"/v1/music/{other}\";\nconst FILES_PREFIX = \"/v1/files/\";\nconst FILES_OTHER_LABEL = \"/v1/files/{other}\";\nconst BATCHES_PREFIX = \"/v1/batches/\";\nconst BATCHES_OTHER_LABEL = \"/v1/batches/{other}\";\nconst VECTOR_STORES_PREFIX = \"/v1/vector_stores/\";\nconst VECTOR_STORES_OTHER_LABEL = \"/v1/vector_stores/{other}\";\n\n/**\n * Label for a path no route claimed. Metrics are recorded for EVERY response,\n * the generic 404 included, so an unrouted path is caller-controlled text and\n * a fuzzer (or a typo'd SDK base URL) would mint one label per request.\n *\n * The collapse is decided by ROUTE SHAPE, never by status: a 404 is not the\n * only way an unrouted path gets answered (a CORS preflight is 204 for any\n * path, a torn-down response has no status at all), and a routed path can\n * legitimately 404 (a fixture miss, a missing file) without becoming\n * unknown. Every path that is not claimed by a placeholder rule above must\n * therefore be listed here to keep its verbatim label.\n */\nexport const UNKNOWN_PATH_LABEL = \"{unknown}\";\n\n/**\n * Static paths server.ts dispatches by exact match (after\n * `normalizeCompatPath`, so the canonical `/v1/...` spelling). A path absent\n * from this table that no placeholder rule claims is labelled `{unknown}`\n * whatever its status — so a new static route needs an entry here or its\n * traffic is counted but not named.\n */\nconst STATIC_ROUTE_PATHS = new Set([\n \"/health\",\n \"/ready\",\n \"/metrics\",\n \"/search\",\n \"/v1/chat/completions\",\n \"/v1/responses\",\n \"/v1/realtime\",\n \"/v1/messages\",\n \"/v1/embeddings\",\n \"/v1/moderations\",\n \"/v1/models\",\n \"/v1/_requests\",\n \"/v1/images/generations\",\n \"/v1/images/edits\",\n \"/v1/images/variations\",\n \"/v1/audio/speech\",\n \"/v1/audio/transcriptions\",\n \"/v1/audio/translations\",\n \"/v1/videos\",\n \"/v1/batches\",\n \"/v1/files\",\n \"/v1/vector_stores\",\n \"/v1/sound-generation\",\n \"/v1/music\",\n \"/v1/text-to-voice\",\n \"/v1/text-to-voice/design\",\n \"/v1beta/interactions\",\n \"/v2/chat\",\n \"/v2/embed\",\n \"/v2/rerank\",\n \"/api/chat\",\n \"/api/generate\",\n \"/api/embeddings\",\n \"/api/embed\",\n \"/api/tags\",\n \"/api/v1/videos\",\n \"/api/v1/videos/models\",\n \"/api/v1/credits\",\n \"/api/v1/key\",\n \"/api/v1/models\",\n \"/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent\",\n]);\n\n/**\n * Control API (`/__aimock/...`): a closed set of sub-paths, matched exactly\n * in `handleControlAPI`; anything else under the prefix collapses.\n */\nconst CONTROL_PREFIX = \"/__aimock/\";\nconst CONTROL_PATHS = new Set([\n \"/__aimock/health\",\n \"/__aimock/journal\",\n \"/__aimock/fixtures\",\n \"/__aimock/chaos\",\n \"/__aimock/reset\",\n \"/__aimock/reset/journal\",\n \"/__aimock/reset/fixtures\",\n \"/__aimock/error\",\n \"/__aimock/openapi.json\",\n \"/__aimock/routes\",\n]);\nconst CONTROL_OTHER_LABEL = \"/__aimock/{other}\";\n\n/**\n * Sub-paths a mounted service (`createServer(..., mounts)`) answers below its\n * mount root. The mount roots themselves are runtime configuration, so the\n * caller passes them in; the JSON-RPC handlers (MCP, A2A, AG-UI, vector) take\n * the root only, and A2A additionally serves its agent card.\n */\nconst MOUNT_SUBPATHS = new Set([\"/.well-known/agent-card.json\"]);\n\n/**\n * Status label for a response that was destroyed before its body ended: no\n * status line reached the client as a completed response, so no HTTP code\n * describes the outcome truthfully. Distinct from every numeric status so\n * `sum(aimock_requests_total)` equals requests served.\n */\nexport const DESTROYED_STATUS_LABEL = \"destroyed\";\n\n/**\n * Fine-tuning routes. Both id families (`ftjob-…` per create, `ftckpt-…` per\n * checkpoint) are minted per resource, so every id-bearing path has to collapse\n * or each job and each checkpoint mints its own label pair.\n *\n * The label hazard is NOT limited to the routes this server implements: metrics\n * are recorded on `res.on(\"finish\")` for EVERY response, the generic 404\n * included, and `openai@4.104.0` calls a good deal more of the namespace than\n * aimock handles. Enumerated from that SDK:\n *\n * - `resources/fine-tuning/jobs/jobs.js` — `/fine_tuning/jobs`,\n * `…/jobs/{id}`, `…/jobs/{id}/cancel`, `…/jobs/{id}/pause`,\n * `…/jobs/{id}/resume`\n * - `resources/fine-tuning/jobs/checkpoints.js` — `…/jobs/{id}/checkpoints`\n * - `resources/fine-tuning/checkpoints/permissions.js` —\n * `…/checkpoints/{ckpt}/permissions` (create/list) and\n * `…/checkpoints/{ckpt}/permissions/{id}` (delete)\n * - `resources/fine-tuning/alpha/graders.js` —\n * `/fine_tuning/alpha/graders/run`, `/fine_tuning/alpha/graders/validate`\n *\n * The grader routes carry no ids, so they need no placeholder — but they DO\n * need naming here, because the namespace cascade below ends in a catch-all\n * and a static route must not be swallowed by it.\n *\n * Every segment after `/v1/fine_tuning/` is caller-controlled, so the rule is:\n * known action names are kept verbatim, ids collapse to `{id}`/`{ckpt}`, and\n * anything else — an unknown action, an unknown depth — collapses to a single\n * bucket. That is what actually keeps the fine-tuning label set finite no\n * matter what is requested; an un-collapsed tail would leave the hole open to\n * a typo or a fuzzer.\n *\n * Not exported: server.ts dispatches fine-tuning jobs from the same\n * route-registry.ts bindings (`FINE_TUNING_JOBS_PATH`, `FINE_TUNING_ID_RE`,\n * `FINE_TUNING_CANCEL_RE`, `FINE_TUNING_EVENTS_RE`).\n */\nconst FINE_TUNING_PREFIX = \"/v1/fine_tuning/\";\nconst FINE_TUNING_STATIC_PATHS = new Set([\n \"/v1/fine_tuning/jobs\",\n \"/v1/fine_tuning/alpha/graders/run\",\n \"/v1/fine_tuning/alpha/graders/validate\",\n]);\nconst FINE_TUNING_SUBRESOURCE_RE = /^\\/v1\\/fine_tuning\\/jobs\\/[^/]+\\/([^/]+)$/;\nconst FINE_TUNING_SUBRESOURCES = new Set([\"cancel\", \"events\", \"pause\", \"resume\", \"checkpoints\"]);\nconst FINE_TUNING_PERMISSION_ID_RE = /^\\/v1\\/fine_tuning\\/checkpoints\\/[^/]+\\/permissions\\/[^/]+$/;\nconst FINE_TUNING_CHECKPOINT_SUBRESOURCE_RE = /^\\/v1\\/fine_tuning\\/checkpoints\\/[^/]+\\/([^/]+)$/;\nconst FINE_TUNING_CHECKPOINT_SUBRESOURCES = new Set([\"permissions\"]);\nconst FINE_TUNING_CHECKPOINT_ID_RE = /^\\/v1\\/fine_tuning\\/checkpoints\\/([^/]+)$/;\nconst FINE_TUNING_OTHER_LABEL = \"/v1/fine_tuning/{other}\";\n\n/**\n * Normalize parametric API paths to route patterns for use as metric labels.\n * Replaces dynamic segments (model IDs, deployment names, etc.) with placeholders.\n * `mountPaths` are the roots of the mounted services, so their traffic keeps a\n * bounded label instead of collapsing to `{unknown}`.\n */\nexport function normalizePathLabel(pathname: string, mountPaths: readonly string[] = []): string {\n // Match server dispatch precedence: controls, mounts in registration order,\n // then provider routes. A mount may overlap any provider namespace.\n // Control API: exact sub-paths verbatim, anything else under the prefix\n // collapses.\n if (pathname.startsWith(CONTROL_PREFIX)) {\n return CONTROL_PATHS.has(pathname) ? pathname : CONTROL_OTHER_LABEL;\n }\n\n // Mounted services: the root and the known sub-paths verbatim, anything\n // deeper collapses under the mount.\n for (const mountPath of mountPaths) {\n if (pathname === mountPath) return pathname;\n if (pathname.startsWith(mountPath + \"/\")) {\n return MOUNT_SUBPATHS.has(pathname.slice(mountPath.length))\n ? pathname\n : `${mountPath}/{other}`;\n }\n }\n\n // Bedrock: /model/{modelId}/{operation}\n const bedrockMatch = pathname.match(BEDROCK_RE);\n if (bedrockMatch) {\n return `/model/{modelId}/${bedrockMatch[2]}`;\n }\n\n // Gemini: /v1beta/models/{model}:{action}, plus the bare GET model lookup.\n const geminiMatch = pathname.match(GEMINI_RE);\n if (geminiMatch) {\n const action = GEMINI_ACTIONS.has(geminiMatch[2]) ? geminiMatch[2] : \"{action}\";\n return `/v1beta/models/{model}:${action}`;\n }\n if (GEMINI_MODEL_RE.test(pathname)) {\n return \"/v1beta/models/{model}\";\n }\n\n // Azure: /openai/deployments/{id}/{operation}\n const azureMatch = pathname.match(AZURE_RE);\n if (azureMatch) {\n return `/openai/deployments/{id}/${azureMatch[2]}`;\n }\n\n // Vertex AI: /v1/projects/{p}/locations/{l}/publishers/google/models/{m}:{action}\n const vertexMatch = pathname.match(VERTEX_RE);\n if (vertexMatch) {\n const action = VERTEX_ACTIONS.has(vertexMatch[4]) ? vertexMatch[4] : \"{action}\";\n return `/v1/projects/{p}/locations/{l}/publishers/google/models/{m}:${action}`;\n }\n\n // ElevenLabs TTS: /v1/text-to-speech/{voice_id}\n if (ELEVENLABS_TTS_RE.test(pathname)) {\n return \"/v1/text-to-speech/{voice_id}\";\n }\n\n // ElevenLabs voices: /v1/voices/{voice_id}\n if (ELEVENLABS_VOICE_RE.test(pathname)) {\n return \"/v1/voices/{voice_id}\";\n }\n\n // OpenRouter video: /api/v1/videos/{jobId}[/content] — jobIds are random\n // UUIDs, so raw paths would mint unbounded label cardinality. The static\n // /api/v1/videos/models listing route must not collapse into {jobId}.\n if (OPENROUTER_VIDEO_CONTENT_RE.test(pathname)) {\n return \"/api/v1/videos/{jobId}/content\";\n }\n if (pathname !== \"/api/v1/videos/models\" && OPENROUTER_VIDEO_STATUS_RE.test(pathname)) {\n return \"/api/v1/videos/{jobId}\";\n }\n\n // Google Veo video: submit `:predictLongRunning` + poll `/v1beta/operations/{name}`.\n // Operation names are random UUIDs, so the raw path would mint unbounded\n // label cardinality.\n if (VEO_PREDICT_LRO_RE.test(pathname)) {\n return \"/v1beta/models/{model}:predictLongRunning\";\n }\n if (VEO_OPERATION_RE.test(pathname)) {\n return \"/v1beta/operations/{name}\";\n }\n\n // xAI Grok Imagine submit is a static literal path — keep it distinct from\n // the `/v1/videos/{id}` status label below (which it would otherwise collapse\n // into), exactly as `/api/v1/videos/models` is kept out of the jobId bucket.\n if (pathname === GROK_VIDEO_SUBMIT_PATH) {\n return GROK_VIDEO_SUBMIT_PATH;\n }\n\n // OpenAI/Grok video status: /v1/videos/{id}\n if (OPENAI_VIDEO_STATUS_RE.test(pathname)) {\n return \"/v1/videos/{id}\";\n }\n\n // BytePlus Ark video: /[api/v3/]contents/generations/tasks[/{id}] — task ids\n // (`cgt-…`) are unbounded cardinality. Status before submit: the status RE is\n // the more specific of the two and the submit RE cannot match a trailing id\n // segment, so the order is belt-and-braces. Appended at the end of the\n // cascade because the anchored REs above cannot match any path an earlier\n // entry claims.\n if (BYTEPLUS_VIDEO_STATUS_RE.test(pathname)) {\n return \"/contents/generations/tasks/{id}\";\n }\n if (BYTEPLUS_VIDEO_SUBMIT_RE.test(pathname)) {\n return \"/contents/generations/tasks\";\n }\n\n if (BATCHES_CANCEL_RE.test(pathname)) {\n return \"/v1/batches/{id}/cancel\";\n }\n if (pathname !== \"/v1/batches\" && BATCHES_ID_RE.test(pathname)) {\n return \"/v1/batches/{id}\";\n }\n if (pathname.startsWith(BATCHES_PREFIX)) {\n return BATCHES_OTHER_LABEL;\n }\n\n // Files API: /v1/files/{id} and /v1/files/{id}/content carry random\n // `file-…` ids — raw paths would mint unbounded label cardinality.\n // Content before id: the id RE would otherwise swallow the content suffix.\n if (FILES_CONTENT_RE.test(pathname)) {\n return \"/v1/files/{id}/content\";\n }\n if (pathname !== \"/v1/files\" && FILES_ID_RE.test(pathname)) {\n return \"/v1/files/{id}\";\n }\n if (pathname.startsWith(FILES_PREFIX)) {\n return FILES_OTHER_LABEL;\n }\n\n // Vector Stores: /v1/vector_stores/{id}[/files[/{fileId}]|/file_batches…|/search].\n // Most-specific first so the id RE cannot swallow a sub-resource suffix.\n if (VECTOR_STORES_SEARCH_RE.test(pathname)) {\n return \"/v1/vector_stores/{id}/search\";\n }\n if (VECTOR_STORES_BATCH_FILES_RE.test(pathname)) {\n return \"/v1/vector_stores/{id}/file_batches/{batchId}/files\";\n }\n if (VECTOR_STORES_BATCH_CANCEL_RE.test(pathname)) {\n return \"/v1/vector_stores/{id}/file_batches/{batchId}/cancel\";\n }\n if (VECTOR_STORES_BATCH_RE.test(pathname)) {\n return \"/v1/vector_stores/{id}/file_batches/{batchId}\";\n }\n if (VECTOR_STORES_FILE_RE.test(pathname)) {\n return \"/v1/vector_stores/{id}/files/{fileId}\";\n }\n if (VECTOR_STORES_FILES_RE.test(pathname)) {\n return \"/v1/vector_stores/{id}/files\";\n }\n if (VECTOR_STORES_FILE_BATCHES_RE.test(pathname)) {\n return \"/v1/vector_stores/{id}/file_batches\";\n }\n if (pathname !== \"/v1/vector_stores\" && VECTOR_STORES_ID_RE.test(pathname)) {\n return \"/v1/vector_stores/{id}\";\n }\n if (pathname.startsWith(VECTOR_STORES_PREFIX)) {\n return VECTOR_STORES_OTHER_LABEL;\n }\n\n // fal.ai: see the namespace note above. Queue-request rules read before the\n // path-mirror rule because `/fal/queue/requests/…` also matches the mirror\n // RE's `.+/requests/` shape; both would label correctly, but the specific\n // rule reading first keeps the cascade legible.\n if (FAL_ROUTE_RE.test(pathname)) {\n const queueRequest = pathname.match(FAL_QUEUE_REQUEST_RE);\n if (queueRequest) {\n return `/fal/queue/requests/{id}${falRequestSuffix(queueRequest[1])}`;\n }\n if (FAL_QUEUE_SUBMIT_RE.test(pathname)) return \"/fal/queue/submit/{model}\";\n if (FAL_RUN_RE.test(pathname)) return \"/fal/run/{model}\";\n const mirrorRequest = pathname.match(FAL_MIRROR_REQUEST_RE);\n if (mirrorRequest) {\n return `/fal/{model}/requests/{id}${falRequestSuffix(mirrorRequest[1])}`;\n }\n return FAL_OTHER_LABEL;\n }\n\n // ElevenLabs Music: known actions verbatim, anything else collapses.\n if (pathname.startsWith(MUSIC_PREFIX)) {\n return MUSIC_ACTIONS.has(pathname.slice(MUSIC_PREFIX.length)) ? pathname : MUSIC_OTHER_LABEL;\n }\n\n // Fine-tuning. Handled as one closed namespace rather than a few loose REs:\n // the cascade is entered by prefix and always returns, so no fine-tuning path\n // can reach the verbatim return at the bottom of this function.\n //\n // Order matters twice. The id-bearing permission rule reads before the\n // checkpoint sub-resource rule, because `…/permissions/{id}` would otherwise\n // never be reached (its own second segment is the id). Within each family the\n // sub-resource rule reads before the id rule; the id REs are anchored to a\n // single trailing segment so the two cannot both match, but the more specific\n // path reading first is what makes the cascade legible.\n if (pathname.startsWith(FINE_TUNING_PREFIX)) {\n if (FINE_TUNING_STATIC_PATHS.has(pathname)) return pathname;\n\n const ftSubresource = pathname.match(FINE_TUNING_SUBRESOURCE_RE);\n if (ftSubresource) {\n const action = FINE_TUNING_SUBRESOURCES.has(ftSubresource[1]) ? ftSubresource[1] : \"{action}\";\n return `/v1/fine_tuning/jobs/{id}/${action}`;\n }\n if (FINE_TUNING_ID_RE.test(pathname)) return \"/v1/fine_tuning/jobs/{id}\";\n\n if (FINE_TUNING_PERMISSION_ID_RE.test(pathname)) {\n return \"/v1/fine_tuning/checkpoints/{ckpt}/permissions/{id}\";\n }\n const ckptSubresource = pathname.match(FINE_TUNING_CHECKPOINT_SUBRESOURCE_RE);\n if (ckptSubresource) {\n const action = FINE_TUNING_CHECKPOINT_SUBRESOURCES.has(ckptSubresource[1])\n ? ckptSubresource[1]\n : \"{action}\";\n return `/v1/fine_tuning/checkpoints/{ckpt}/${action}`;\n }\n if (FINE_TUNING_CHECKPOINT_ID_RE.test(pathname)) {\n return \"/v1/fine_tuning/checkpoints/{ckpt}\";\n }\n\n return FINE_TUNING_OTHER_LABEL;\n }\n\n // Static path — verbatim only if server.ts routes it. Anything else is, as\n // far as metrics can tell, caller-controlled text, whatever status it got\n // (404, a 204 preflight, a destroyed response); every routed id path that\n // can legitimately 404 has already returned its placeholder label above.\n return STATIC_ROUTE_PATHS.has(pathname) ? pathname : UNKNOWN_PATH_LABEL;\n}\n\nfunction falRequestSuffix(sub: string | undefined): string {\n if (sub === undefined) return \"\";\n return FAL_REQUEST_SUBRESOURCES.has(sub) ? `/${sub}` : \"/{other}\";\n}\n"],"mappings":"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"}
package/dist/ollama.cjs CHANGED
@@ -445,7 +445,11 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
445
445
  } }));
446
446
  return;
447
447
  }
448
- const chatShapeError = require_helpers.validateChatMessages(ollamaReq.messages) ?? require_helpers.validateToolsField(ollamaReq.tools);
448
+ let chatShapeError = require_helpers.validateChatMessages(ollamaReq.messages) ?? require_helpers.validateToolsField(ollamaReq.tools);
449
+ if (!chatShapeError && Array.isArray(ollamaReq.tools)) {
450
+ const missingFunctionIndex = ollamaReq.tools.findIndex((tool) => tool.function === null || tool.function === void 0);
451
+ if (missingFunctionIndex !== -1) chatShapeError = `tools[${missingFunctionIndex}].function must be an object`;
452
+ }
449
453
  if (chatShapeError) {
450
454
  journal.add({
451
455
  method: req.method ?? "POST",
@@ -1 +1 @@
1
- {"version":3,"file":"ollama.cjs","names":["resolveFixtureBlocks","flattenHeaders","isJsonObject","validateChatMessages","validateToolsField","getContext","getTestId","matchFixtureDiagnostic","recordMatchOptions","applyChaosAsync","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","proxyAndRecord","resolveResponse","isErrorResponse","serializeErrorResponse","isContentWithToolCallsResponse","resolveReasoningForModel","createInterruptionSignal","writeNDJSONStream","isTextResponse","isToolCallResponse","normalizeEmbeddingInput","isEmbeddingResponse","generateDeterministicEmbedding"],"sources":["../src/ollama.ts"],"sourcesContent":["/**\n * Ollama API endpoint support.\n *\n * Translates incoming /api/chat, /api/generate, and /api/embeddings requests\n * into the 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 * - Embeddings return a single `embedding` array, not an array of objects\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n FixtureBlock,\n HandlerDefaults,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n isEmbeddingResponse,\n validateChatMessages,\n validateToolsField,\n normalizeEmbeddingInput,\n resolveFixtureBlocks,\n serializeErrorResponse,\n generateDeterministicEmbedding,\n flattenHeaders,\n isJsonObject,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n resolveReasoningForModel,\n strictOverrideField,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic, recordMatchOptions } 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 { applyChaosAsync } 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 tool_calls?: Array<{ function: { name: string; arguments: unknown } }>;\n images?: 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 system?: string;\n images?: string[];\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 const chatMsg: ChatMessage = {\n role: msg.role as ChatMessage[\"role\"],\n content: msg.content,\n };\n\n // Map inbound tool_calls on assistant messages to the internal format\n if (msg.tool_calls && msg.tool_calls.length > 0) {\n chatMsg.tool_calls = msg.tool_calls.map((tc, i) => ({\n id: `call_${i}`,\n type: \"function\" as const,\n function: {\n name: tc.function.name,\n arguments:\n typeof tc.function.arguments === \"string\"\n ? tc.function.arguments\n : JSON.stringify(tc.function.arguments),\n },\n }));\n }\n\n messages.push(chatMsg);\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 const messages: ChatMessage[] = [];\n\n // Prepend system message if present\n if (req.system) {\n messages.push({ role: \"system\", content: req.system });\n }\n\n messages.push({ role: \"user\", content: req.prompt });\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 };\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 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 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 created_at: createdAt,\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 created_at: createdAt,\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 created_at: new Date().toISOString(),\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 chunkSize: number,\n logger: Logger,\n reasoning?: string,\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 const createdAt = new Date().toISOString();\n\n // Reasoning chunks (before tool calls)\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 message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\n\n chunks.push({\n model,\n created_at: createdAt,\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 created_at: createdAt,\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 reasoning?: string,\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 created_at: new Date().toISOString(),\n message: {\n role: \"assistant\",\n content: \"\",\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n tool_calls: ollamaToolCalls,\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\n// ─── Response builders: /api/chat — content + tool calls ────────────────────\n\n// Map a fixture tool call into Ollama's wire shape (object arguments, no id).\nfunction toOllamaToolCall(\n tc: ToolCall,\n logger: Logger,\n): { function: { name: string; arguments: unknown } } {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(`Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`);\n argsObj = {};\n }\n return { function: { name: tc.name, arguments: argsObj } };\n}\n\nfunction buildOllamaChatContentWithToolCallsChunks(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n logger: Logger,\n reasoning?: string,\n blocks?: FixtureBlock[],\n): object[] {\n const chunks: object[] = [];\n const createdAt = new Date().toISOString();\n\n // ── Ordered-blocks path ──────────────────────────────────────────────────\n // When the fixture declares explicit `blocks`, stream NDJSON message chunks\n // following the blocks' ARRAY ORDER: a text block emits a `message.content`\n // delta chunk; a toolCall block emits a chunk carrying `message.tool_calls`.\n // So [toolCall, text] puts the tool_call-bearing chunk before the content\n // chunk. Ollama tool-first ordering is PARTIALLY observable: the chunk order\n // on the wire is honored, but some Ollama clients reassemble content and\n // tool_calls positionally (text first regardless), so downstream order is\n // best-effort. Reasoning chunks (if any) still lead, matching legacy. The\n // legacy single-chunk-all-tools path stays untouched on the else branch.\n if (blocks && blocks.length > 0) {\n const ordered = resolveFixtureBlocks(blocks);\n\n // Reasoning chunks (before everything else), identical to legacy.\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 message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\n\n for (const block of ordered) {\n if (block.type === \"text\") {\n for (let i = 0; i < block.text.length; i += chunkSize) {\n const slice = block.text.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: slice },\n done: false,\n });\n }\n } else {\n chunks.push({\n model,\n created_at: createdAt,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: [toOllamaToolCall(block, logger)],\n },\n done: false,\n });\n }\n }\n\n // Final chunk — preserved exactly as legacy (done + timing fields).\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n }\n\n // ── Legacy path (UNCHANGED) ──────────────────────────────────────────────\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 message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\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 created_at: createdAt,\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 created_at: createdAt,\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 created_at: createdAt,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\n// NOTE (#274): this NON-streaming Ollama builder is intentionally degenerate\n// w.r.t. `blocks` ORDERING. Ollama's non-streaming chat response puts `content`\n// and `tool_calls` in SEPARATE fields on a single `message` object — they are\n// NOT a positionally-observable array, so a tool-first `blocks` fixture cannot\n// be expressed in the wire shape. Block ORDER is therefore a no-op here.\n// The PAYLOAD, however, is still derived from `blocks` when present (the body\n// backfills `content`/`tool_calls` from the blocks — see the comment at the\n// top of the function body); only the relative ordering of those fields is\n// unobservable. (Order-observable surfaces — Claude `content[]`, Gemini\n// `parts[]`, Responses `output[]` — DO honor block order; see those builders.)\nfunction buildOllamaChatContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n reasoning?: string,\n blocks?: FixtureBlock[],\n): object {\n // Blocks-only / blocks-present fixtures: the non-streaming wire shape has no\n // positional array, so order is a no-op here (see NOTE above). But the PAYLOAD\n // must not be dropped: backfill `content` from text blocks (concatenated) and\n // `tool_calls` from toolCall blocks, mirroring what the streaming path derives.\n // Legacy (no-blocks) callers keep byte-identical output.\n if (blocks && blocks.length > 0) {\n const ordered = resolveFixtureBlocks(blocks);\n content = ordered\n .filter((b): b is { type: \"text\"; text: string } => b.type === \"text\")\n .map((b) => b.text)\n .join(\"\");\n toolCalls = ordered\n .filter(\n (b): b is { type: \"toolCall\"; name: string; arguments: string; id?: string } =>\n b.type === \"toolCall\",\n )\n .map((b) => ({ name: b.name, arguments: b.arguments }));\n }\n\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 created_at: new Date().toISOString(),\n message: {\n role: \"assistant\",\n content,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\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 (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\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 body: ${detail}`,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Reject non-object bodies (e.g. `null`) before touching fields —\n // otherwise `ollamaReq.messages` throws a TypeError that surfaces as a 500\n // instead of a 400.\n if (!isJsonObject(ollamaReq)) {\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: \"Request body must be a JSON object\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Reject wrong-typed message/tool fields before the converter dereferences\n // them. A missing/non-array `messages` keeps the historic message below.\n const chatShapeError =\n validateChatMessages(ollamaReq.messages) ?? validateToolsField(ollamaReq.tools);\n if (chatShapeError) {\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: ${chatShapeError}`,\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 completionReq._context = getContext(req);\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n // Record mode proxies on a miss to capture a fresh turn (see record gate\n // below), so keep turnIndex strict to prevent an earlier-turn fixture from\n // shadowing a longer request and skipping the new turn's recording.\n recordMatchOptions(!!defaults.record, defaults.logger),\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 await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\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 const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictStatus = 503;\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n logger.error(strictNoMatchLogLine(req.method ?? \"POST\", urlPath, skippedBySequenceOrTurn));\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: strictStatus,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\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 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 === \"handled_by_hook\") return;\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 journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\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, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n // Content + tool calls response (must be checked before text/tool-only branches)\n if (isContentWithToolCallsResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Ollama API -- ignoring\");\n }\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 // Gate reasoning emission on the requested model's capability (aimock#254).\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n logger,\n );\n if (!streaming) {\n const body = buildOllamaChatContentWithToolCallsResponse(\n response.content ?? \"\",\n response.toolCalls ?? [],\n completionReq.model,\n logger,\n effReasoning,\n response.blocks,\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 effReasoning,\n response.blocks,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\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 if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Ollama API -- ignoring\");\n }\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 // Gate reasoning emission on the requested model's capability (aimock#254).\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n logger,\n );\n if (!streaming) {\n const body = buildOllamaChatTextResponse(response.content, completionReq.model, effReasoning);\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 effReasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\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 if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Ollama API — ignoring\");\n }\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 // Gate reasoning emission on the requested model's capability (aimock#254).\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n logger,\n );\n if (!streaming) {\n const body = buildOllamaChatToolCallResponse(\n response.toolCalls,\n completionReq.model,\n logger,\n effReasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatToolCallChunks(\n response.toolCalls,\n completionReq.model,\n chunkSize,\n logger,\n effReasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\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 (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\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 body: ${detail}`,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Reject non-object bodies (e.g. `null`) before touching fields —\n // otherwise `generateReq.prompt` throws a TypeError that surfaces as a 500\n // instead of a 400.\n if (!isJsonObject(generateReq)) {\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: \"Request body must be a JSON object\",\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 completionReq._context = getContext(req);\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n // Record mode proxies on a miss to capture a fresh turn (see record gate\n // below), so keep turnIndex strict to prevent an earlier-turn fixture from\n // shadowing a longer request and skipping the new turn's recording.\n recordMatchOptions(!!defaults.record, defaults.logger),\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 await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\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 const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictStatus = 503;\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(\n strictNoMatchLogLine(req.method ?? \"POST\", urlPath, skippedBySequenceOrTurn),\n );\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: strictStatus,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\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 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 === \"handled_by_hook\") return;\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 journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\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, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\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 // Gate reasoning emission on the requested model's capability (aimock#254).\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n defaults.logger,\n );\n if (!streaming) {\n const body = buildOllamaGenerateTextResponse(\n response.content,\n completionReq.model,\n effReasoning,\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 effReasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\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\n// ─── Ollama embeddings request type ─────────────────────────────────────────\n\ninterface OllamaEmbeddingsRequest {\n model: string;\n prompt?: string;\n input?: string | string[];\n}\n\n// ─── Request handler: /api/embeddings ───────────────────────────────────────\n\nexport async function handleOllamaEmbeddings(\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/embeddings\";\n\n let embReq: OllamaEmbeddingsRequest;\n try {\n embReq = JSON.parse(raw) as OllamaEmbeddingsRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\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 body: ${detail}`,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Reject non-object bodies (e.g. `null`) before touching fields —\n // otherwise `embReq.prompt` throws a TypeError that surfaces as a 500\n // instead of a 400.\n if (!isJsonObject(embReq)) {\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: \"Request body must be a JSON object\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Reject wrong-typed prompt/input before `.slice()` / `.join()` below read\n // them — a number passes the presence checks and then throws a TypeError.\n // `null` means \"absent\" on both fields, matching the `??` fall-through below.\n // `input` is checked with the repo's shared `normalizeEmbeddingInput`, which\n // also accepts the pre-tokenized `number[]` / `number[][]` shapes.\n const promptShapeError =\n embReq.prompt !== undefined && embReq.prompt !== null && typeof embReq.prompt !== \"string\"\n ? \"prompt field must be a string\"\n : embReq.input !== undefined &&\n embReq.input !== null &&\n normalizeEmbeddingInput(embReq.input) === null\n ? \"input field must be a string or an array of strings\"\n : null;\n if (promptShapeError) {\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: ${promptShapeError}`,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Ollama accepts either \"prompt\" or \"input\" for the text to embed\n const inputText =\n embReq.prompt ??\n (typeof embReq.input === \"string\" ? embReq.input : undefined) ??\n (Array.isArray(embReq.input) ? embReq.input.join(\" \") : undefined);\n\n if (!embReq.model) {\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: model field is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!inputText) {\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 or input field is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Build synthetic ChatCompletionRequest for the fixture router\n const syntheticReq: ChatCompletionRequest = {\n model: embReq.model,\n messages: [],\n embeddingInput: inputText,\n _endpointType: \"embedding\",\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n // Record mode proxies on a miss to capture a fresh turn (see record gate\n // below), so keep turnIndex strict to prevent an earlier-turn fixture from\n // shadowing a longer request and skipping the new turn's recording.\n recordMatchOptions(!!defaults.record, defaults.logger),\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 await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n {\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (fixture) {\n const response = await resolveResponse(fixture, syntheticReq);\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: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n // Embedding response — use the fixture's embedding\n if (isEmbeddingResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n const body = {\n model: embReq.model,\n embedding: [...response.embedding],\n };\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Fixture matched but response type is not compatible with embeddings\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message:\n \"Fixture response did not match any known embedding type (must have embedding or error)\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — check strict mode first, then try proxy\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n logger.error(strictNoMatchLogLine(req.method ?? \"POST\", urlPath, skippedBySequenceOrTurn));\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"ollama\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n // No fixture match — generate deterministic embedding from input text\n logger.warn(\n `No embedding fixture matched for \"${inputText.slice(0, 80)}\" — returning deterministic fallback`,\n );\n const embedding = generateDeterministicEmbedding(inputText);\n\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n\n const body = {\n model: embReq.model,\n embedding,\n };\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ollama.cjs","names":["resolveFixtureBlocks","flattenHeaders","isJsonObject","validateChatMessages","validateToolsField","getContext","getTestId","matchFixtureDiagnostic","recordMatchOptions","applyChaosAsync","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","proxyAndRecord","resolveResponse","isErrorResponse","serializeErrorResponse","isContentWithToolCallsResponse","resolveReasoningForModel","createInterruptionSignal","writeNDJSONStream","isTextResponse","isToolCallResponse","normalizeEmbeddingInput","isEmbeddingResponse","generateDeterministicEmbedding"],"sources":["../src/ollama.ts"],"sourcesContent":["/**\n * Ollama API endpoint support.\n *\n * Translates incoming /api/chat, /api/generate, and /api/embeddings requests\n * into the 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 * - Embeddings return a single `embedding` array, not an array of objects\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n FixtureBlock,\n HandlerDefaults,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n isEmbeddingResponse,\n validateChatMessages,\n validateToolsField,\n normalizeEmbeddingInput,\n resolveFixtureBlocks,\n serializeErrorResponse,\n generateDeterministicEmbedding,\n flattenHeaders,\n isJsonObject,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n resolveReasoningForModel,\n strictOverrideField,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic, recordMatchOptions } 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 { applyChaosAsync } 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 tool_calls?: Array<{ function: { name: string; arguments: unknown } }>;\n images?: 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 system?: string;\n images?: string[];\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 const chatMsg: ChatMessage = {\n role: msg.role as ChatMessage[\"role\"],\n content: msg.content,\n };\n\n // Map inbound tool_calls on assistant messages to the internal format\n if (msg.tool_calls && msg.tool_calls.length > 0) {\n chatMsg.tool_calls = msg.tool_calls.map((tc, i) => ({\n id: `call_${i}`,\n type: \"function\" as const,\n function: {\n name: tc.function.name,\n arguments:\n typeof tc.function.arguments === \"string\"\n ? tc.function.arguments\n : JSON.stringify(tc.function.arguments),\n },\n }));\n }\n\n messages.push(chatMsg);\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 const messages: ChatMessage[] = [];\n\n // Prepend system message if present\n if (req.system) {\n messages.push({ role: \"system\", content: req.system });\n }\n\n messages.push({ role: \"user\", content: req.prompt });\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 };\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 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 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 created_at: createdAt,\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 created_at: createdAt,\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 created_at: new Date().toISOString(),\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 chunkSize: number,\n logger: Logger,\n reasoning?: string,\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 const createdAt = new Date().toISOString();\n\n // Reasoning chunks (before tool calls)\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 message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\n\n chunks.push({\n model,\n created_at: createdAt,\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 created_at: createdAt,\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 reasoning?: string,\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 created_at: new Date().toISOString(),\n message: {\n role: \"assistant\",\n content: \"\",\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n tool_calls: ollamaToolCalls,\n },\n done: true,\n ...DURATION_FIELDS,\n };\n}\n\n// ─── Response builders: /api/chat — content + tool calls ────────────────────\n\n// Map a fixture tool call into Ollama's wire shape (object arguments, no id).\nfunction toOllamaToolCall(\n tc: ToolCall,\n logger: Logger,\n): { function: { name: string; arguments: unknown } } {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(`Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`);\n argsObj = {};\n }\n return { function: { name: tc.name, arguments: argsObj } };\n}\n\nfunction buildOllamaChatContentWithToolCallsChunks(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n logger: Logger,\n reasoning?: string,\n blocks?: FixtureBlock[],\n): object[] {\n const chunks: object[] = [];\n const createdAt = new Date().toISOString();\n\n // ── Ordered-blocks path ──────────────────────────────────────────────────\n // When the fixture declares explicit `blocks`, stream NDJSON message chunks\n // following the blocks' ARRAY ORDER: a text block emits a `message.content`\n // delta chunk; a toolCall block emits a chunk carrying `message.tool_calls`.\n // So [toolCall, text] puts the tool_call-bearing chunk before the content\n // chunk. Ollama tool-first ordering is PARTIALLY observable: the chunk order\n // on the wire is honored, but some Ollama clients reassemble content and\n // tool_calls positionally (text first regardless), so downstream order is\n // best-effort. Reasoning chunks (if any) still lead, matching legacy. The\n // legacy single-chunk-all-tools path stays untouched on the else branch.\n if (blocks && blocks.length > 0) {\n const ordered = resolveFixtureBlocks(blocks);\n\n // Reasoning chunks (before everything else), identical to legacy.\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 message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\n\n for (const block of ordered) {\n if (block.type === \"text\") {\n for (let i = 0; i < block.text.length; i += chunkSize) {\n const slice = block.text.slice(i, i + chunkSize);\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: slice },\n done: false,\n });\n }\n } else {\n chunks.push({\n model,\n created_at: createdAt,\n message: {\n role: \"assistant\",\n content: \"\",\n tool_calls: [toOllamaToolCall(block, logger)],\n },\n done: false,\n });\n }\n }\n\n // Final chunk — preserved exactly as legacy (done + timing fields).\n chunks.push({\n model,\n created_at: createdAt,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n }\n\n // ── Legacy path (UNCHANGED) ──────────────────────────────────────────────\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 message: { role: \"assistant\", content: \"\", reasoning_content: slice },\n done: false,\n });\n }\n }\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 created_at: createdAt,\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 created_at: createdAt,\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 created_at: createdAt,\n message: { role: \"assistant\", content: \"\" },\n done: true,\n ...DURATION_FIELDS,\n });\n\n return chunks;\n}\n\n// NOTE (#274): this NON-streaming Ollama builder is intentionally degenerate\n// w.r.t. `blocks` ORDERING. Ollama's non-streaming chat response puts `content`\n// and `tool_calls` in SEPARATE fields on a single `message` object — they are\n// NOT a positionally-observable array, so a tool-first `blocks` fixture cannot\n// be expressed in the wire shape. Block ORDER is therefore a no-op here.\n// The PAYLOAD, however, is still derived from `blocks` when present (the body\n// backfills `content`/`tool_calls` from the blocks — see the comment at the\n// top of the function body); only the relative ordering of those fields is\n// unobservable. (Order-observable surfaces — Claude `content[]`, Gemini\n// `parts[]`, Responses `output[]` — DO honor block order; see those builders.)\nfunction buildOllamaChatContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n reasoning?: string,\n blocks?: FixtureBlock[],\n): object {\n // Blocks-only / blocks-present fixtures: the non-streaming wire shape has no\n // positional array, so order is a no-op here (see NOTE above). But the PAYLOAD\n // must not be dropped: backfill `content` from text blocks (concatenated) and\n // `tool_calls` from toolCall blocks, mirroring what the streaming path derives.\n // Legacy (no-blocks) callers keep byte-identical output.\n if (blocks && blocks.length > 0) {\n const ordered = resolveFixtureBlocks(blocks);\n content = ordered\n .filter((b): b is { type: \"text\"; text: string } => b.type === \"text\")\n .map((b) => b.text)\n .join(\"\");\n toolCalls = ordered\n .filter(\n (b): b is { type: \"toolCall\"; name: string; arguments: string; id?: string } =>\n b.type === \"toolCall\",\n )\n .map((b) => ({ name: b.name, arguments: b.arguments }));\n }\n\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 created_at: new Date().toISOString(),\n message: {\n role: \"assistant\",\n content,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\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 (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\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 body: ${detail}`,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Reject non-object bodies (e.g. `null`) before touching fields —\n // otherwise `ollamaReq.messages` throws a TypeError that surfaces as a 500\n // instead of a 400.\n if (!isJsonObject(ollamaReq)) {\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: \"Request body must be a JSON object\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Reject wrong-typed message/tool fields before the converter dereferences\n // them. A missing/non-array `messages` keeps the historic message below.\n let chatShapeError =\n validateChatMessages(ollamaReq.messages) ?? validateToolsField(ollamaReq.tools);\n if (!chatShapeError && Array.isArray(ollamaReq.tools)) {\n const missingFunctionIndex = ollamaReq.tools.findIndex(\n (tool) => tool.function === null || tool.function === undefined,\n );\n if (missingFunctionIndex !== -1) {\n chatShapeError = `tools[${missingFunctionIndex}].function must be an object`;\n }\n }\n if (chatShapeError) {\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: ${chatShapeError}`,\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 completionReq._context = getContext(req);\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n // Record mode proxies on a miss to capture a fresh turn (see record gate\n // below), so keep turnIndex strict to prevent an earlier-turn fixture from\n // shadowing a longer request and skipping the new turn's recording.\n recordMatchOptions(!!defaults.record, defaults.logger),\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 await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\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 const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictStatus = 503;\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n logger.error(strictNoMatchLogLine(req.method ?? \"POST\", urlPath, skippedBySequenceOrTurn));\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: strictStatus,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\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 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 === \"handled_by_hook\") return;\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 journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\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, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n // Content + tool calls response (must be checked before text/tool-only branches)\n if (isContentWithToolCallsResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Ollama API -- ignoring\");\n }\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 // Gate reasoning emission on the requested model's capability (aimock#254).\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n logger,\n );\n if (!streaming) {\n const body = buildOllamaChatContentWithToolCallsResponse(\n response.content ?? \"\",\n response.toolCalls ?? [],\n completionReq.model,\n logger,\n effReasoning,\n response.blocks,\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 effReasoning,\n response.blocks,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\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 if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Ollama API -- ignoring\");\n }\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 // Gate reasoning emission on the requested model's capability (aimock#254).\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n logger,\n );\n if (!streaming) {\n const body = buildOllamaChatTextResponse(response.content, completionReq.model, effReasoning);\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 effReasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\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 if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Ollama API — ignoring\");\n }\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 // Gate reasoning emission on the requested model's capability (aimock#254).\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n logger,\n );\n if (!streaming) {\n const body = buildOllamaChatToolCallResponse(\n response.toolCalls,\n completionReq.model,\n logger,\n effReasoning,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildOllamaChatToolCallChunks(\n response.toolCalls,\n completionReq.model,\n chunkSize,\n logger,\n effReasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\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 (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\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 body: ${detail}`,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Reject non-object bodies (e.g. `null`) before touching fields —\n // otherwise `generateReq.prompt` throws a TypeError that surfaces as a 500\n // instead of a 400.\n if (!isJsonObject(generateReq)) {\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: \"Request body must be a JSON object\",\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 completionReq._context = getContext(req);\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n // Record mode proxies on a miss to capture a fresh turn (see record gate\n // below), so keep turnIndex strict to prevent an earlier-turn fixture from\n // shadowing a longer request and skipping the new turn's recording.\n recordMatchOptions(!!defaults.record, defaults.logger),\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 await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\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 const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictStatus = 503;\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(\n strictNoMatchLogLine(req.method ?? \"POST\", urlPath, skippedBySequenceOrTurn),\n );\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: strictStatus,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\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 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 === \"handled_by_hook\") return;\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 journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify({\n error: {\n message: \"No fixture matched\",\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, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\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 // Gate reasoning emission on the requested model's capability (aimock#254).\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n completionReq.model,\n effectiveStrict,\n defaults.logger,\n );\n if (!streaming) {\n const body = buildOllamaGenerateTextResponse(\n response.content,\n completionReq.model,\n effReasoning,\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 effReasoning,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeNDJSONStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,\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\n// ─── Ollama embeddings request type ─────────────────────────────────────────\n\ninterface OllamaEmbeddingsRequest {\n model: string;\n prompt?: string;\n input?: string | string[];\n}\n\n// ─── Request handler: /api/embeddings ───────────────────────────────────────\n\nexport async function handleOllamaEmbeddings(\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/embeddings\";\n\n let embReq: OllamaEmbeddingsRequest;\n try {\n embReq = JSON.parse(raw) as OllamaEmbeddingsRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\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 body: ${detail}`,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Reject non-object bodies (e.g. `null`) before touching fields —\n // otherwise `embReq.prompt` throws a TypeError that surfaces as a 500\n // instead of a 400.\n if (!isJsonObject(embReq)) {\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: \"Request body must be a JSON object\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Reject wrong-typed prompt/input before `.slice()` / `.join()` below read\n // them — a number passes the presence checks and then throws a TypeError.\n // `null` means \"absent\" on both fields, matching the `??` fall-through below.\n // `input` is checked with the repo's shared `normalizeEmbeddingInput`, which\n // also accepts the pre-tokenized `number[]` / `number[][]` shapes.\n const promptShapeError =\n embReq.prompt !== undefined && embReq.prompt !== null && typeof embReq.prompt !== \"string\"\n ? \"prompt field must be a string\"\n : embReq.input !== undefined &&\n embReq.input !== null &&\n normalizeEmbeddingInput(embReq.input) === null\n ? \"input field must be a string or an array of strings\"\n : null;\n if (promptShapeError) {\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: ${promptShapeError}`,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Ollama accepts either \"prompt\" or \"input\" for the text to embed\n const inputText =\n embReq.prompt ??\n (typeof embReq.input === \"string\" ? embReq.input : undefined) ??\n (Array.isArray(embReq.input) ? embReq.input.join(\" \") : undefined);\n\n if (!embReq.model) {\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: model field is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (!inputText) {\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 or input field is required\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Build synthetic ChatCompletionRequest for the fixture router\n const syntheticReq: ChatCompletionRequest = {\n model: embReq.model,\n messages: [],\n embeddingInput: inputText,\n _endpointType: \"embedding\",\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n // Record mode proxies on a miss to capture a fresh turn (see record gate\n // below), so keep turnIndex strict to prevent an earlier-turn fixture from\n // shadowing a longer request and skipping the new turn's recording.\n recordMatchOptions(!!defaults.record, defaults.logger),\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 await applyChaosAsync(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n {\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (fixture) {\n const response = await resolveResponse(fixture, syntheticReq);\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: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n // Embedding response — use the fixture's embedding\n if (isEmbeddingResponse(response)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n const body = {\n model: embReq.model,\n embedding: [...response.embedding],\n };\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n return;\n }\n\n // Fixture matched but response type is not compatible with embeddings\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message:\n \"Fixture response did not match any known embedding type (must have embedding or error)\",\n type: \"server_error\",\n },\n }),\n );\n return;\n }\n\n // No fixture match — check strict mode first, then try proxy\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n logger.error(strictNoMatchLogLine(req.method ?? \"POST\", urlPath, skippedBySequenceOrTurn));\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n syntheticReq,\n \"ollama\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n });\n return;\n }\n }\n\n // No fixture match — generate deterministic embedding from input text\n logger.warn(\n `No embedding fixture matched for \"${inputText.slice(0, 80)}\" — returning deterministic fallback`,\n );\n const embedding = generateDeterministicEmbedding(inputText);\n\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture: null },\n });\n\n const body = {\n model: embReq.model,\n embedding,\n };\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ollama.d.cts","names":[],"sources":["../src/ollama.ts"],"sourcesContent":[],"mappings":";;;;;;AAwnBA,UA7jBU,aAAA,CA6jBwB;EAAA,IAAA,EAAA,QAAA,GAAA,MAAA,GAAA,WAAA,GAAA,MAAA;SAC3B,EAAA,MAAK;YACA,CAAA,EA5jBG,KA4jBH,CAAA;IAEA,QAAA,EAAA;MACD,IAAA,EAAA,MAAA;MACC,SAAA,EAAA,OAAA;IACY,CAAA;;EACd,MAAA,CAAA,EAAA,MAAA,EAAA;AA4ZV;UA19BU,aAAA,CA09BgC;MACnC,EAAA,MAAK;UACL,EAAK;IAEA,IAAA,EAAA,MAAA;IACD,WAAA,CAAA,EAAA,MAAA;IACC,UAAA,CAAA,EAAA,MAAA;;;UAv9BF,aAAA,CAy9BA;;YAv9BE;;;;;;UAGF;;iBA0BM,yBAAA,MAA+B,gBAAgB;iBA8gBzC,YAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B;iBA4ZmB,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":";;;;;;AAwnBA,UA7jBU,aAAA,CA6jBwB;EAAA,IAAA,EAAA,QAAA,GAAA,MAAA,GAAA,WAAA,GAAA,MAAA;SAC3B,EAAA,MAAK;YACA,CAAA,EA5jBG,KA4jBH,CAAA;IAEA,QAAA,EAAA;MACD,IAAA,EAAA,MAAA;MACC,SAAA,EAAA,OAAA;IACY,CAAA;;EACd,MAAA,CAAA,EAAA,MAAA,EAAA;AAoaV;UAl+BU,aAAA,CAk+BgC;MACnC,EAAA,MAAK;UACL,EAAK;IAEA,IAAA,EAAA,MAAA;IACD,WAAA,CAAA,EAAA,MAAA;IACC,UAAA,CAAA,EAAA,MAAA;;;UA/9BF,aAAA,CAi+BA;;YA/9BE;;;;;;UAGF;;iBA0BM,yBAAA,MAA+B,gBAAgB;iBA8gBzC,YAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B;iBAoamB,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":";;;;;;AAwnBA,UA7jBU,aAAA,CA6jBwB;EAAA,IAAA,EAAA,QAAA,GAAA,MAAA,GAAA,WAAA,GAAA,MAAA;SAC3B,EAAA,MAAK;YACA,CAAA,EA5jBG,KA4jBH,CAAA;IAEA,QAAA,EAAA;MACD,IAAA,EAAA,MAAA;MACC,SAAA,EAAA,OAAA;IACY,CAAA;;EACd,MAAA,CAAA,EAAA,MAAA,EAAA;AA4ZV;UA19BU,aAAA,CA09BgC;MACnC,EAAA,MAAK;UACL,EAAK;IAEA,IAAA,EAAA,MAAA;IACD,WAAA,CAAA,EAAA,MAAA;IACC,UAAA,CAAA,EAAA,MAAA;;;UAv9BF,aAAA,CAy9BA;;YAv9BE;;;;;;UAGF;;iBA0BM,yBAAA,MAA+B,gBAAgB;iBA8gBzC,YAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B;iBA4ZmB,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":";;;;;;AAwnBA,UA7jBU,aAAA,CA6jBwB;EAAA,IAAA,EAAA,QAAA,GAAA,MAAA,GAAA,WAAA,GAAA,MAAA;SAC3B,EAAA,MAAK;YACA,CAAA,EA5jBG,KA4jBH,CAAA;IAEA,QAAA,EAAA;MACD,IAAA,EAAA,MAAA;MACC,SAAA,EAAA,OAAA;IACY,CAAA;;EACd,MAAA,CAAA,EAAA,MAAA,EAAA;AAoaV;UAl+BU,aAAA,CAk+BgC;MACnC,EAAA,MAAK;UACL,EAAK;IAEA,IAAA,EAAA,MAAA;IACD,WAAA,CAAA,EAAA,MAAA;IACC,UAAA,CAAA,EAAA,MAAA;;;UA/9BF,aAAA,CAi+BA;;YA/9BE;;;;;;UAGF;;iBA0BM,yBAAA,MAA+B,gBAAgB;iBA8gBzC,YAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B;iBAoamB,oBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B"}
package/dist/ollama.js CHANGED
@@ -445,7 +445,11 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
445
445
  } }));
446
446
  return;
447
447
  }
448
- const chatShapeError = validateChatMessages(ollamaReq.messages) ?? validateToolsField(ollamaReq.tools);
448
+ let chatShapeError = validateChatMessages(ollamaReq.messages) ?? validateToolsField(ollamaReq.tools);
449
+ if (!chatShapeError && Array.isArray(ollamaReq.tools)) {
450
+ const missingFunctionIndex = ollamaReq.tools.findIndex((tool) => tool.function === null || tool.function === void 0);
451
+ if (missingFunctionIndex !== -1) chatShapeError = `tools[${missingFunctionIndex}].function must be an object`;
452
+ }
449
453
  if (chatShapeError) {
450
454
  journal.add({
451
455
  method: req.method ?? "POST",