@copilotkit/aimock 1.46.0 → 1.47.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 (183) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +12 -1
  4. package/README.md +4 -1
  5. package/dist/aws-event-stream.cjs +20 -23
  6. package/dist/aws-event-stream.cjs.map +1 -1
  7. package/dist/aws-event-stream.d.cts +0 -1
  8. package/dist/aws-event-stream.d.ts +0 -1
  9. package/dist/aws-event-stream.js +20 -24
  10. package/dist/aws-event-stream.js.map +1 -1
  11. package/dist/bedrock-converse.cjs +79 -82
  12. package/dist/bedrock-converse.cjs.map +1 -1
  13. package/dist/bedrock-converse.d.cts.map +1 -1
  14. package/dist/bedrock-converse.d.ts.map +1 -1
  15. package/dist/bedrock-converse.js +80 -83
  16. package/dist/bedrock-converse.js.map +1 -1
  17. package/dist/bedrock.cjs +112 -121
  18. package/dist/bedrock.cjs.map +1 -1
  19. package/dist/bedrock.d.cts.map +1 -1
  20. package/dist/bedrock.d.ts.map +1 -1
  21. package/dist/bedrock.js +113 -122
  22. package/dist/bedrock.js.map +1 -1
  23. package/dist/cohere.cjs +58 -106
  24. package/dist/cohere.cjs.map +1 -1
  25. package/dist/cohere.d.cts.map +1 -1
  26. package/dist/cohere.d.ts.map +1 -1
  27. package/dist/cohere.js +60 -108
  28. package/dist/cohere.js.map +1 -1
  29. package/dist/config-loader.d.cts.map +1 -1
  30. package/dist/config-loader.d.ts.map +1 -1
  31. package/dist/fixture-loader.cjs +103 -2
  32. package/dist/fixture-loader.cjs.map +1 -1
  33. package/dist/fixture-loader.d.cts.map +1 -1
  34. package/dist/fixture-loader.d.ts.map +1 -1
  35. package/dist/fixture-loader.js +104 -4
  36. package/dist/fixture-loader.js.map +1 -1
  37. package/dist/gemini-interactions.cjs +63 -121
  38. package/dist/gemini-interactions.cjs.map +1 -1
  39. package/dist/gemini-interactions.d.cts.map +1 -1
  40. package/dist/gemini-interactions.d.ts.map +1 -1
  41. package/dist/gemini-interactions.js +65 -123
  42. package/dist/gemini-interactions.js.map +1 -1
  43. package/dist/gemini.cjs +242 -155
  44. package/dist/gemini.cjs.map +1 -1
  45. package/dist/gemini.d.cts.map +1 -1
  46. package/dist/gemini.d.ts.map +1 -1
  47. package/dist/gemini.js +244 -157
  48. package/dist/gemini.js.map +1 -1
  49. package/dist/helpers.cjs +400 -7
  50. package/dist/helpers.cjs.map +1 -1
  51. package/dist/helpers.d.cts +51 -1
  52. package/dist/helpers.d.cts.map +1 -1
  53. package/dist/helpers.d.ts +51 -1
  54. package/dist/helpers.d.ts.map +1 -1
  55. package/dist/helpers.js +391 -8
  56. package/dist/helpers.js.map +1 -1
  57. package/dist/index.cjs +4 -2
  58. package/dist/index.d.cts +4 -4
  59. package/dist/index.d.ts +4 -4
  60. package/dist/index.js +4 -4
  61. package/dist/mcp-fakes.d.cts.map +1 -1
  62. package/dist/messages.cjs +159 -141
  63. package/dist/messages.cjs.map +1 -1
  64. package/dist/messages.d.cts.map +1 -1
  65. package/dist/messages.d.ts.map +1 -1
  66. package/dist/messages.js +161 -143
  67. package/dist/messages.js.map +1 -1
  68. package/dist/misbehavior.cjs +62 -3
  69. package/dist/misbehavior.cjs.map +1 -1
  70. package/dist/misbehavior.d.cts.map +1 -1
  71. package/dist/misbehavior.d.ts.map +1 -1
  72. package/dist/misbehavior.js +63 -4
  73. package/dist/misbehavior.js.map +1 -1
  74. package/dist/ndjson-writer.cjs +21 -23
  75. package/dist/ndjson-writer.cjs.map +1 -1
  76. package/dist/ndjson-writer.d.cts +0 -1
  77. package/dist/ndjson-writer.d.cts.map +1 -1
  78. package/dist/ndjson-writer.d.ts +0 -1
  79. package/dist/ndjson-writer.d.ts.map +1 -1
  80. package/dist/ndjson-writer.js +21 -24
  81. package/dist/ndjson-writer.js.map +1 -1
  82. package/dist/ollama.cjs +153 -147
  83. package/dist/ollama.cjs.map +1 -1
  84. package/dist/ollama.d.cts.map +1 -1
  85. package/dist/ollama.d.ts.map +1 -1
  86. package/dist/ollama.js +155 -149
  87. package/dist/ollama.js.map +1 -1
  88. package/dist/openrouter-chat.cjs +1 -1
  89. package/dist/openrouter-chat.js +1 -1
  90. package/dist/raw-validation.cjs +177 -0
  91. package/dist/raw-validation.cjs.map +1 -0
  92. package/dist/raw-validation.js +176 -0
  93. package/dist/raw-validation.js.map +1 -0
  94. package/dist/response/deliver.cjs +144 -0
  95. package/dist/response/deliver.cjs.map +1 -0
  96. package/dist/response/deliver.js +143 -0
  97. package/dist/response/deliver.js.map +1 -0
  98. package/dist/response/frame-loop.cjs +102 -0
  99. package/dist/response/frame-loop.cjs.map +1 -0
  100. package/dist/response/frame-loop.js +100 -0
  101. package/dist/response/frame-loop.js.map +1 -0
  102. package/dist/response/lifecycle.cjs +223 -0
  103. package/dist/response/lifecycle.cjs.map +1 -0
  104. package/dist/response/lifecycle.js +211 -0
  105. package/dist/response/lifecycle.js.map +1 -0
  106. package/dist/response/proxy.cjs +71 -0
  107. package/dist/response/proxy.cjs.map +1 -0
  108. package/dist/response/proxy.js +70 -0
  109. package/dist/response/proxy.js.map +1 -0
  110. package/dist/response/raw.cjs +228 -0
  111. package/dist/response/raw.cjs.map +1 -0
  112. package/dist/response/raw.js +227 -0
  113. package/dist/response/raw.js.map +1 -0
  114. package/dist/response/timing.cjs +157 -0
  115. package/dist/response/timing.cjs.map +1 -0
  116. package/dist/response/timing.js +150 -0
  117. package/dist/response/timing.js.map +1 -0
  118. package/dist/responses.cjs +269 -148
  119. package/dist/responses.cjs.map +1 -1
  120. package/dist/responses.d.cts.map +1 -1
  121. package/dist/responses.d.ts.map +1 -1
  122. package/dist/responses.js +270 -149
  123. package/dist/responses.js.map +1 -1
  124. package/dist/router.cjs +20 -4
  125. package/dist/router.cjs.map +1 -1
  126. package/dist/router.d.cts +7 -1
  127. package/dist/router.d.cts.map +1 -1
  128. package/dist/router.d.ts +7 -1
  129. package/dist/router.d.ts.map +1 -1
  130. package/dist/router.js +21 -5
  131. package/dist/router.js.map +1 -1
  132. package/dist/server.cjs +89 -120
  133. package/dist/server.cjs.map +1 -1
  134. package/dist/server.d.cts.map +1 -1
  135. package/dist/server.d.ts.map +1 -1
  136. package/dist/server.js +91 -122
  137. package/dist/server.js.map +1 -1
  138. package/dist/sse-writer.cjs +49 -30
  139. package/dist/sse-writer.cjs.map +1 -1
  140. package/dist/sse-writer.d.cts +0 -1
  141. package/dist/sse-writer.d.cts.map +1 -1
  142. package/dist/sse-writer.d.ts +0 -1
  143. package/dist/sse-writer.d.ts.map +1 -1
  144. package/dist/sse-writer.js +49 -31
  145. package/dist/sse-writer.js.map +1 -1
  146. package/dist/transcription.cjs +19 -33
  147. package/dist/transcription.cjs.map +1 -1
  148. package/dist/transcription.d.cts.map +1 -1
  149. package/dist/transcription.d.ts.map +1 -1
  150. package/dist/transcription.js +20 -34
  151. package/dist/transcription.js.map +1 -1
  152. package/dist/types.cjs.map +1 -1
  153. package/dist/types.d.cts +173 -10
  154. package/dist/types.d.cts.map +1 -1
  155. package/dist/types.d.ts +173 -10
  156. package/dist/types.d.ts.map +1 -1
  157. package/dist/types.js.map +1 -1
  158. package/dist/ws-framing.cjs +12 -6
  159. package/dist/ws-framing.cjs.map +1 -1
  160. package/dist/ws-framing.d.cts +2 -0
  161. package/dist/ws-framing.d.cts.map +1 -1
  162. package/dist/ws-framing.d.ts +2 -0
  163. package/dist/ws-framing.d.ts.map +1 -1
  164. package/dist/ws-framing.js +12 -6
  165. package/dist/ws-framing.js.map +1 -1
  166. package/dist/ws-gemini-live.cjs +120 -93
  167. package/dist/ws-gemini-live.cjs.map +1 -1
  168. package/dist/ws-gemini-live.js +120 -93
  169. package/dist/ws-gemini-live.js.map +1 -1
  170. package/dist/ws-realtime.cjs +80 -70
  171. package/dist/ws-realtime.cjs.map +1 -1
  172. package/dist/ws-realtime.d.cts.map +1 -1
  173. package/dist/ws-realtime.d.ts.map +1 -1
  174. package/dist/ws-realtime.js +80 -70
  175. package/dist/ws-realtime.js.map +1 -1
  176. package/dist/ws-responses.cjs +72 -53
  177. package/dist/ws-responses.cjs.map +1 -1
  178. package/dist/ws-responses.d.cts.map +1 -1
  179. package/dist/ws-responses.d.ts.map +1 -1
  180. package/dist/ws-responses.js +73 -54
  181. package/dist/ws-responses.js.map +1 -1
  182. package/package.json +1 -1
  183. package/skills/write-fixtures/SKILL.md +34 -2
@@ -1 +1 @@
1
- {"version":3,"file":"ndjson-writer.cjs","names":["calculateDelay","delay"],"sources":["../src/ndjson-writer.ts"],"sourcesContent":["/**\n * NDJSON streaming writer for Ollama endpoints.\n *\n * Mirrors writeSSEStream from sse-writer.ts but writes newline-delimited JSON\n * (one JSON object per line) instead of SSE events.\n */\n\nimport type * as http from \"node:http\";\nimport type { StreamingProfile, RecordedTimings } from \"./types.js\";\nimport { delay, calculateDelay } from \"./sse-writer.js\";\n\nexport interface NDJSONStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nexport async function writeNDJSONStream(\n res: http.ServerResponse,\n chunks: object[],\n options?: NDJSONStreamOptions,\n): Promise<boolean> {\n const opts = options ?? {};\n const latency = opts.latency ?? 0;\n const profile = opts.streamingProfile;\n const { recordedTimings, replaySpeed } = opts;\n const signal = opts.signal;\n const onChunkSent = opts.onChunkSent;\n\n if (res.writableEnded) return true;\n res.setHeader(\"Content-Type\", \"application/x-ndjson\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"keep-alive\");\n\n let chunkIndex = 0;\n for (const chunk of chunks) {\n const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);\n if (chunkDelay > 0) {\n await delay(chunkDelay, signal);\n }\n if (signal?.aborted) return false;\n if (res.writableEnded) return true;\n res.write(JSON.stringify(chunk) + \"\\n\");\n onChunkSent?.();\n if (signal?.aborted) return false;\n chunkIndex++;\n }\n\n if (!res.writableEnded) {\n res.end();\n }\n return true;\n}\n"],"mappings":";;;AAoBA,eAAsB,kBACpB,KACA,QACA,SACkB;CAClB,MAAM,OAAO,WAAW,EAAE;CAC1B,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,UAAU,KAAK;CACrB,MAAM,EAAE,iBAAiB,gBAAgB;CACzC,MAAM,SAAS,KAAK;CACpB,MAAM,cAAc,KAAK;AAEzB,KAAI,IAAI,cAAe,QAAO;AAC9B,KAAI,UAAU,gBAAgB,uBAAuB;AACrD,KAAI,UAAU,iBAAiB,WAAW;AAC1C,KAAI,UAAU,cAAc,aAAa;CAEzC,IAAI,aAAa;AACjB,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,aAAaA,kCAAe,YAAY,SAAS,SAAS,iBAAiB,YAAY;AAC7F,MAAI,aAAa,EACf,OAAMC,yBAAM,YAAY,OAAO;AAEjC,MAAI,QAAQ,QAAS,QAAO;AAC5B,MAAI,IAAI,cAAe,QAAO;AAC9B,MAAI,MAAM,KAAK,UAAU,MAAM,GAAG,KAAK;AACvC,iBAAe;AACf,MAAI,QAAQ,QAAS,QAAO;AAC5B;;AAGF,KAAI,CAAC,IAAI,cACP,KAAI,KAAK;AAEX,QAAO"}
1
+ {"version":3,"file":"ndjson-writer.cjs","names":["runFrameLoop","httpSink"],"sources":["../src/ndjson-writer.ts"],"sourcesContent":["/**\n * NDJSON streaming writer for Ollama endpoints.\n *\n * Mirrors writeSSEStream from sse-writer.ts but writes newline-delimited JSON\n * (one JSON object per line) instead of SSE events.\n */\n\nimport type * as http from \"node:http\";\nimport type { StreamingProfile, RecordedTimings } from \"./types.js\";\nimport { httpSink, runFrameLoop, type FrameCodec } from \"./response/frame-loop.js\";\n\nexport interface NDJSONStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nexport async function writeNDJSONStream(\n res: http.ServerResponse,\n chunks: object[],\n options?: NDJSONStreamOptions,\n): Promise<boolean> {\n const opts = options ?? {};\n // Base contract (public API): with no frames, an aborted signal does not\n // stop the writer; it still finishes the stream (any trailer, then end())\n // and returns true. The frame loop returns false there, so pass it no\n // signal when there are no frames (it reads the signal only per frame and\n // after the loop).\n return runFrameLoop(\n httpSink(res),\n chunks,\n ndjsonCodec(),\n {\n latency: opts.latency,\n streamingProfile: opts.streamingProfile,\n recordedTimings: opts.recordedTimings,\n replaySpeed: opts.replaySpeed,\n },\n { signal: chunks.length > 0 ? opts.signal : undefined, onChunkSent: opts.onChunkSent },\n );\n}\n\n// Internal (not part of the public API): newline-delimited JSON, one object\n// per line, no trailer (Ollama). Line comments, not JSDoc, so the bundled\n// .d.ts does not attach this comment to a public declaration.\nexport function ndjsonCodec<F = object>(): FrameCodec<F> {\n return {\n headers: {\n \"Content-Type\": \"application/x-ndjson\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n encode: (frame) => JSON.stringify(frame) + \"\\n\",\n };\n}\n"],"mappings":";;;AAoBA,eAAsB,kBACpB,KACA,QACA,SACkB;CAClB,MAAM,OAAO,WAAW,EAAE;AAM1B,QAAOA,gCACLC,4BAAS,IAAI,EACb,QACA,aAAa,EACb;EACE,SAAS,KAAK;EACd,kBAAkB,KAAK;EACvB,iBAAiB,KAAK;EACtB,aAAa,KAAK;EACnB,EACD;EAAE,QAAQ,OAAO,SAAS,IAAI,KAAK,SAAS;EAAW,aAAa,KAAK;EAAa,CACvF;;AAMH,SAAgB,cAAyC;AACvD,QAAO;EACL,SAAS;GACP,gBAAgB;GAChB,iBAAiB;GACjB,YAAY;GACb;EACD,SAAS,UAAU,KAAK,UAAU,MAAM,GAAG;EAC5C"}
@@ -12,7 +12,6 @@ interface NDJSONStreamOptions {
12
12
  onChunkSent?: () => void;
13
13
  }
14
14
  declare function writeNDJSONStream(res: http$1.ServerResponse, chunks: object[], options?: NDJSONStreamOptions): Promise<boolean>;
15
- //# sourceMappingURL=ndjson-writer.d.ts.map
16
15
  //#endregion
17
16
  export { NDJSONStreamOptions, writeNDJSONStream };
18
17
  //# sourceMappingURL=ndjson-writer.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ndjson-writer.d.cts","names":[],"sources":["../src/ndjson-writer.ts"],"sourcesContent":[],"mappings":";;;;;AAgBW,UALM,mBAAA,CAKN;EAAW,OAAA,CAAA,EAAA,MAAA;EAIA,gBAAA,CAAA,EAPD,gBAOkB;EAAA,eAAA,CAAA,EANnB,eAMmB;aAC3B,CAAA,EAAA,MAAA;QAEA,CAAA,EAPD,WAOC;aACT,CAAA,EAAA,GAAA,GAAA,IAAA;;iBAJmB,iBAAA,MACf,MAAA,CAAK,4CAEA,sBACT"}
1
+ {"version":3,"file":"ndjson-writer.d.cts","names":[],"sources":["../src/ndjson-writer.ts"],"sourcesContent":[],"mappings":";;;;;AAgBsB,UALL,mBAAA,CAKK;EAIA,OAAA,CAAA,EAAA,MAAA;EAAiB,gBAAA,CAAA,EAPlB,gBAOkB;iBAC3B,CAAA,EAPQ,eAOR;aAEA,CAAA,EAAA,MAAA;QACT,CAAA,EARQ,WAQR;EAAO,WAAA,CAAA,EAAA,GAAA,GAAA,IAAA;;iBAJY,iBAAA,MACf,MAAA,CAAK,4CAEA,sBACT"}
@@ -12,7 +12,6 @@ interface NDJSONStreamOptions {
12
12
  onChunkSent?: () => void;
13
13
  }
14
14
  declare function writeNDJSONStream(res: http$1.ServerResponse, chunks: object[], options?: NDJSONStreamOptions): Promise<boolean>;
15
- //# sourceMappingURL=ndjson-writer.d.ts.map
16
15
  //#endregion
17
16
  export { NDJSONStreamOptions, writeNDJSONStream };
18
17
  //# sourceMappingURL=ndjson-writer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ndjson-writer.d.ts","names":[],"sources":["../src/ndjson-writer.ts"],"sourcesContent":[],"mappings":";;;;;AAgBW,UALM,mBAAA,CAKN;EAAW,OAAA,CAAA,EAAA,MAAA;EAIA,gBAAA,CAAA,EAPD,gBAOkB;EAAA,eAAA,CAAA,EANnB,eAMmB;aAC3B,CAAA,EAAA,MAAA;QAEA,CAAA,EAPD,WAOC;aACT,CAAA,EAAA,GAAA,GAAA,IAAA;;iBAJmB,iBAAA,MACf,MAAA,CAAK,4CAEA,sBACT"}
1
+ {"version":3,"file":"ndjson-writer.d.ts","names":[],"sources":["../src/ndjson-writer.ts"],"sourcesContent":[],"mappings":";;;;;AAgBsB,UALL,mBAAA,CAKK;EAIA,OAAA,CAAA,EAAA,MAAA;EAAiB,gBAAA,CAAA,EAPlB,gBAOkB;iBAC3B,CAAA,EAPQ,eAOR;aAEA,CAAA,EAAA,MAAA;QACT,CAAA,EARQ,WAQR;EAAO,WAAA,CAAA,EAAA,GAAA,GAAA,IAAA;;iBAJY,iBAAA,MACf,MAAA,CAAK,4CAEA,sBACT"}
@@ -1,32 +1,29 @@
1
- import { calculateDelay, delay } from "./sse-writer.js";
1
+ import { httpSink, runFrameLoop } from "./response/frame-loop.js";
2
2
 
3
3
  //#region src/ndjson-writer.ts
4
4
  async function writeNDJSONStream(res, chunks, options) {
5
5
  const opts = options ?? {};
6
- const latency = opts.latency ?? 0;
7
- const profile = opts.streamingProfile;
8
- const { recordedTimings, replaySpeed } = opts;
9
- const signal = opts.signal;
10
- const onChunkSent = opts.onChunkSent;
11
- if (res.writableEnded) return true;
12
- res.setHeader("Content-Type", "application/x-ndjson");
13
- res.setHeader("Cache-Control", "no-cache");
14
- res.setHeader("Connection", "keep-alive");
15
- let chunkIndex = 0;
16
- for (const chunk of chunks) {
17
- const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);
18
- if (chunkDelay > 0) await delay(chunkDelay, signal);
19
- if (signal?.aborted) return false;
20
- if (res.writableEnded) return true;
21
- res.write(JSON.stringify(chunk) + "\n");
22
- onChunkSent?.();
23
- if (signal?.aborted) return false;
24
- chunkIndex++;
25
- }
26
- if (!res.writableEnded) res.end();
27
- return true;
6
+ return runFrameLoop(httpSink(res), chunks, ndjsonCodec(), {
7
+ latency: opts.latency,
8
+ streamingProfile: opts.streamingProfile,
9
+ recordedTimings: opts.recordedTimings,
10
+ replaySpeed: opts.replaySpeed
11
+ }, {
12
+ signal: chunks.length > 0 ? opts.signal : void 0,
13
+ onChunkSent: opts.onChunkSent
14
+ });
15
+ }
16
+ function ndjsonCodec() {
17
+ return {
18
+ headers: {
19
+ "Content-Type": "application/x-ndjson",
20
+ "Cache-Control": "no-cache",
21
+ Connection: "keep-alive"
22
+ },
23
+ encode: (frame) => JSON.stringify(frame) + "\n"
24
+ };
28
25
  }
29
26
 
30
27
  //#endregion
31
- export { writeNDJSONStream };
28
+ export { ndjsonCodec, writeNDJSONStream };
32
29
  //# sourceMappingURL=ndjson-writer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ndjson-writer.js","names":[],"sources":["../src/ndjson-writer.ts"],"sourcesContent":["/**\n * NDJSON streaming writer for Ollama endpoints.\n *\n * Mirrors writeSSEStream from sse-writer.ts but writes newline-delimited JSON\n * (one JSON object per line) instead of SSE events.\n */\n\nimport type * as http from \"node:http\";\nimport type { StreamingProfile, RecordedTimings } from \"./types.js\";\nimport { delay, calculateDelay } from \"./sse-writer.js\";\n\nexport interface NDJSONStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nexport async function writeNDJSONStream(\n res: http.ServerResponse,\n chunks: object[],\n options?: NDJSONStreamOptions,\n): Promise<boolean> {\n const opts = options ?? {};\n const latency = opts.latency ?? 0;\n const profile = opts.streamingProfile;\n const { recordedTimings, replaySpeed } = opts;\n const signal = opts.signal;\n const onChunkSent = opts.onChunkSent;\n\n if (res.writableEnded) return true;\n res.setHeader(\"Content-Type\", \"application/x-ndjson\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"keep-alive\");\n\n let chunkIndex = 0;\n for (const chunk of chunks) {\n const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);\n if (chunkDelay > 0) {\n await delay(chunkDelay, signal);\n }\n if (signal?.aborted) return false;\n if (res.writableEnded) return true;\n res.write(JSON.stringify(chunk) + \"\\n\");\n onChunkSent?.();\n if (signal?.aborted) return false;\n chunkIndex++;\n }\n\n if (!res.writableEnded) {\n res.end();\n }\n return true;\n}\n"],"mappings":";;;AAoBA,eAAsB,kBACpB,KACA,QACA,SACkB;CAClB,MAAM,OAAO,WAAW,EAAE;CAC1B,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,UAAU,KAAK;CACrB,MAAM,EAAE,iBAAiB,gBAAgB;CACzC,MAAM,SAAS,KAAK;CACpB,MAAM,cAAc,KAAK;AAEzB,KAAI,IAAI,cAAe,QAAO;AAC9B,KAAI,UAAU,gBAAgB,uBAAuB;AACrD,KAAI,UAAU,iBAAiB,WAAW;AAC1C,KAAI,UAAU,cAAc,aAAa;CAEzC,IAAI,aAAa;AACjB,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,aAAa,eAAe,YAAY,SAAS,SAAS,iBAAiB,YAAY;AAC7F,MAAI,aAAa,EACf,OAAM,MAAM,YAAY,OAAO;AAEjC,MAAI,QAAQ,QAAS,QAAO;AAC5B,MAAI,IAAI,cAAe,QAAO;AAC9B,MAAI,MAAM,KAAK,UAAU,MAAM,GAAG,KAAK;AACvC,iBAAe;AACf,MAAI,QAAQ,QAAS,QAAO;AAC5B;;AAGF,KAAI,CAAC,IAAI,cACP,KAAI,KAAK;AAEX,QAAO"}
1
+ {"version":3,"file":"ndjson-writer.js","names":[],"sources":["../src/ndjson-writer.ts"],"sourcesContent":["/**\n * NDJSON streaming writer for Ollama endpoints.\n *\n * Mirrors writeSSEStream from sse-writer.ts but writes newline-delimited JSON\n * (one JSON object per line) instead of SSE events.\n */\n\nimport type * as http from \"node:http\";\nimport type { StreamingProfile, RecordedTimings } from \"./types.js\";\nimport { httpSink, runFrameLoop, type FrameCodec } from \"./response/frame-loop.js\";\n\nexport interface NDJSONStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nexport async function writeNDJSONStream(\n res: http.ServerResponse,\n chunks: object[],\n options?: NDJSONStreamOptions,\n): Promise<boolean> {\n const opts = options ?? {};\n // Base contract (public API): with no frames, an aborted signal does not\n // stop the writer; it still finishes the stream (any trailer, then end())\n // and returns true. The frame loop returns false there, so pass it no\n // signal when there are no frames (it reads the signal only per frame and\n // after the loop).\n return runFrameLoop(\n httpSink(res),\n chunks,\n ndjsonCodec(),\n {\n latency: opts.latency,\n streamingProfile: opts.streamingProfile,\n recordedTimings: opts.recordedTimings,\n replaySpeed: opts.replaySpeed,\n },\n { signal: chunks.length > 0 ? opts.signal : undefined, onChunkSent: opts.onChunkSent },\n );\n}\n\n// Internal (not part of the public API): newline-delimited JSON, one object\n// per line, no trailer (Ollama). Line comments, not JSDoc, so the bundled\n// .d.ts does not attach this comment to a public declaration.\nexport function ndjsonCodec<F = object>(): FrameCodec<F> {\n return {\n headers: {\n \"Content-Type\": \"application/x-ndjson\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n },\n encode: (frame) => JSON.stringify(frame) + \"\\n\",\n };\n}\n"],"mappings":";;;AAoBA,eAAsB,kBACpB,KACA,QACA,SACkB;CAClB,MAAM,OAAO,WAAW,EAAE;AAM1B,QAAO,aACL,SAAS,IAAI,EACb,QACA,aAAa,EACb;EACE,SAAS,KAAK;EACd,kBAAkB,KAAK;EACvB,iBAAiB,KAAK;EACtB,aAAa,KAAK;EACnB,EACD;EAAE,QAAQ,OAAO,SAAS,IAAI,KAAK,SAAS;EAAW,aAAa,KAAK;EAAa,CACvF;;AAMH,SAAgB,cAAyC;AACvD,QAAO;EACL,SAAS;GACP,gBAAgB;GAChB,iBAAiB;GACjB,YAAY;GACb;EACD,SAAS,UAAU,KAAK,UAAU,MAAM,GAAG;EAC5C"}
package/dist/ollama.cjs CHANGED
@@ -7,8 +7,11 @@ const require_misbehavior = require('./misbehavior.cjs');
7
7
  const require_embedding_request = require('./embedding-request.cjs');
8
8
  const require_router = require('./router.cjs');
9
9
  const require_recorder = require('./recorder.cjs');
10
- const require_interruption = require('./interruption.cjs');
10
+ const require_timing = require('./response/timing.cjs');
11
+ const require_deliver = require('./response/deliver.cjs');
12
+ const require_proxy = require('./response/proxy.cjs');
11
13
  const require_ndjson_writer = require('./ndjson-writer.cjs');
14
+ const require_raw = require('./response/raw.cjs');
12
15
  const require_ollama = require('./response/usage/codecs/ollama.cjs');
13
16
 
14
17
  //#region src/ollama.ts
@@ -330,8 +333,10 @@ function prepareOllamaMisbehavior(plan, request, stream, chunkSize, effectiveStr
330
333
  for (const block of outcome.ordered) if (block.type === "text") priorContent += block.text;
331
334
  else if (index++ === plan.target?.index) break;
332
335
  }
336
+ const chunks = stream ? [...buildOllamaChatTextChunks(priorContent, model, chunkSize).slice(0, -1), { error }] : [{ error }];
333
337
  return {
334
- chunks: stream ? [...buildOllamaChatTextChunks(priorContent, model, chunkSize).slice(0, -1), { error }] : [{ error }],
338
+ build: () => chunks,
339
+ reasoning: void 0,
335
340
  status: stream ? 200 : 500,
336
341
  summary: {
337
342
  ...plan.summary,
@@ -347,64 +352,64 @@ function prepareOllamaMisbehavior(plan, request, stream, chunkSize, effectiveStr
347
352
  arguments: parsed.text
348
353
  };
349
354
  });
350
- let chunks;
351
- let reasoning = "";
352
- if (plan.summary.fault === "reasoning-only") {
353
- reasoning = plan.reasoning ?? "";
354
- const base = {
355
- model,
356
- created_at: (/* @__PURE__ */ new Date()).toISOString()
357
- };
358
- if (stream) {
359
- chunks = [];
360
- for (let i = 0; i < reasoning.length; i += chunkSize) chunks.push({
355
+ const reasoning = plan.summary.fault === "reasoning-only" ? plan.reasoning ?? "" : require_helpers.resolveReasoningForModel("reasoning" in response ? response.reasoning : void 0, model, effectiveStrict, logger) ?? "";
356
+ const completion = content + reasoning + servedToolCalls.map((call) => call.name + call.arguments).join("");
357
+ const build = () => {
358
+ let chunks;
359
+ if (plan.summary.fault === "reasoning-only") {
360
+ const base = {
361
+ model,
362
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
363
+ };
364
+ if (stream) {
365
+ chunks = [];
366
+ for (let i = 0; i < reasoning.length; i += chunkSize) chunks.push({
367
+ ...base,
368
+ message: {
369
+ role: "assistant",
370
+ content: "",
371
+ thinking: reasoning.slice(i, i + chunkSize)
372
+ },
373
+ done: false
374
+ });
375
+ chunks.push({
376
+ ...base,
377
+ message: {
378
+ role: "assistant",
379
+ content: ""
380
+ },
381
+ done: true,
382
+ ...DURATION_FIELDS,
383
+ done_reason: "length"
384
+ });
385
+ } else chunks = [{
361
386
  ...base,
362
387
  message: {
363
388
  role: "assistant",
364
389
  content: "",
365
- thinking: reasoning.slice(i, i + chunkSize)
366
- },
367
- done: false
368
- });
369
- chunks.push({
370
- ...base,
371
- message: {
372
- role: "assistant",
373
- content: ""
390
+ thinking: reasoning
374
391
  },
375
392
  done: true,
376
393
  ...DURATION_FIELDS,
377
394
  done_reason: "length"
378
- });
379
- } else chunks = [{
380
- ...base,
381
- message: {
382
- role: "assistant",
383
- content: "",
384
- thinking: reasoning
385
- },
386
- done: true,
387
- ...DURATION_FIELDS,
388
- done_reason: "length"
389
- }];
390
- } else {
391
- reasoning = require_helpers.resolveReasoningForModel("reasoning" in response ? response.reasoning : void 0, model, effectiveStrict, logger) ?? "";
392
- if (combined) chunks = stream ? buildOllamaChatContentWithToolCallsChunks(content, calls, model, chunkSize, reasoning, response.blocks) : [buildOllamaChatContentWithToolCallsResponse(content, calls, model, reasoning, response.blocks)];
395
+ }];
396
+ } else if (combined) chunks = stream ? buildOllamaChatContentWithToolCallsChunks(content, calls, model, chunkSize, reasoning, response.blocks) : [buildOllamaChatContentWithToolCallsResponse(content, calls, model, reasoning, response.blocks)];
393
397
  else if (require_helpers.isToolCallResponse(response)) chunks = stream ? buildOllamaChatToolCallChunks(calls, model, chunkSize, reasoning) : [buildOllamaChatToolCallResponse(calls, model, reasoning)];
394
398
  else chunks = stream ? buildOllamaChatTextChunks(content, model, chunkSize, reasoning) : [buildOllamaChatTextResponse(content, model, reasoning)];
395
- }
396
- const completion = content + reasoning + servedToolCalls.map((call) => call.name + call.arguments).join("");
397
- const terminal = chunks.length - 1;
398
- chunks[terminal] = {
399
- ...chunks[terminal],
400
- ...require_ollama.render({
401
- kind: "applied-chat-estimate",
402
- messages: request.messages,
403
- completion
404
- })
399
+ const terminal = chunks.length - 1;
400
+ chunks[terminal] = {
401
+ ...chunks[terminal],
402
+ ...require_ollama.render({
403
+ kind: "applied-chat-estimate",
404
+ messages: request.messages,
405
+ completion
406
+ })
407
+ };
408
+ return chunks;
405
409
  };
406
410
  return {
407
- chunks,
411
+ build,
412
+ reasoning,
408
413
  status: 200,
409
414
  summary: {
410
415
  ...plan.summary,
@@ -523,7 +528,10 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
523
528
  completionReq._endpointType = "chat";
524
529
  completionReq._context = require_helpers.getContext(req);
525
530
  const testId = require_helpers.getTestId(req);
526
- const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, require_router.recordMatchOptions(!!defaults.record, defaults.logger));
531
+ const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, {
532
+ ...require_router.recordMatchOptions(!!defaults.record, defaults.logger),
533
+ wire: "ollama"
534
+ });
527
535
  if (fixture) {
528
536
  journal.incrementFixtureMatchCount(fixture, fixtures, testId);
529
537
  logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
@@ -557,7 +565,7 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
557
565
  return;
558
566
  }
559
567
  if (defaults.record) {
560
- const outcome = await require_recorder.proxyAndRecord(req, res, completionReq, "ollama", urlPath, fixtures, defaults, raw);
568
+ const { outcome, clientHungUp } = await require_proxy.withHangUpDetection(req, res, () => require_recorder.proxyAndRecord(req, res, completionReq, "ollama", urlPath, fixtures, defaults, raw));
561
569
  if (outcome === "handled_by_hook") return;
562
570
  if (outcome !== "not_configured") {
563
571
  journal.add({
@@ -568,7 +576,8 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
568
576
  response: {
569
577
  status: res.statusCode ?? 200,
570
578
  fixture: null,
571
- source: "proxy"
579
+ source: "proxy",
580
+ ...require_proxy.proxyInterruption(res, clientHungUp)
572
581
  }
573
582
  });
574
583
  return;
@@ -626,11 +635,44 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
626
635
  }).body);
627
636
  return;
628
637
  }
638
+ if (require_helpers.isRawResponse(response)) {
639
+ await require_raw.serveRawResponse({
640
+ wire: "ollama",
641
+ stream: ollamaReq.stream !== false,
642
+ req,
643
+ res,
644
+ fixture,
645
+ response,
646
+ body: completionReq,
647
+ journal,
648
+ defaults,
649
+ testId,
650
+ misbehaviorSummary: misbehavior.summary,
651
+ errorWire: "ollama"
652
+ });
653
+ return;
654
+ }
629
655
  const latency = fixture.latency ?? defaults.latency;
630
656
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
657
+ const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
658
+ const streamTiming = {
659
+ latency,
660
+ streamingProfile: fixture.streamingProfile,
661
+ recordedTimings: fixture.recordedTimings,
662
+ replaySpeed
663
+ };
664
+ const jsonOpts = (payloadLength, duplicateId = false) => ({
665
+ surface: "other",
666
+ payloadLength,
667
+ chunkSize,
668
+ recordedTimings: fixture.recordedTimings,
669
+ duplicateId,
670
+ replaySpeed
671
+ });
631
672
  const streaming = ollamaReq.stream !== false;
632
673
  if (misbehavior.kind === "applied") {
633
674
  const prepared = prepareOllamaMisbehavior(misbehavior, completionReq, streaming, chunkSize, require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression, logger);
675
+ const chunks = !streaming && prepared.status === 200 && require_timing.nonStreamingTimingEnabled(fixture, "other") ? void 0 : prepared.build();
634
676
  const journalEntry = journal.add({
635
677
  method: req.method ?? "POST",
636
678
  path: urlPath,
@@ -647,24 +689,16 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
647
689
  defaults,
648
690
  testId
649
691
  });
650
- if (!streaming) {
692
+ if (!streaming && prepared.status !== 200) {
651
693
  res.writeHead(prepared.status, { "Content-Type": "application/json" });
652
- res.end(JSON.stringify(prepared.chunks[0]));
653
- } else {
654
- const interruption = require_interruption.createInterruptionSignal(fixture);
655
- if (!await require_ndjson_writer.writeNDJSONStream(res, prepared.chunks, {
656
- latency,
657
- streamingProfile: fixture.streamingProfile,
658
- recordedTimings: fixture.recordedTimings,
659
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
660
- signal: interruption?.signal,
661
- onChunkSent: interruption?.tick
662
- })) {
663
- if (!res.writableEnded) res.destroy();
664
- journalEntry.response.interrupted = true;
665
- journalEntry.response.interruptReason = interruption?.reason();
666
- }
667
- interruption?.cleanup();
694
+ res.end(JSON.stringify((chunks ?? prepared.build())[0]));
695
+ } else if (!streaming) await require_deliver.deliverJson(res, journalEntry, fixture, () => (chunks ?? prepared.build())[0], {
696
+ ...jsonOpts(require_timing.misbehaviorPayloadLength(misbehavior, misbehavior.reasoning === void 0 ? prepared.reasoning : void 0), !!misbehavior.duplicateId),
697
+ status: prepared.status
698
+ });
699
+ else {
700
+ const frames = chunks ?? prepared.build();
701
+ await require_deliver.deliverStream(res, journalEntry, fixture, require_ndjson_writer.ndjsonCodec(), frames, streamTiming);
668
702
  }
669
703
  return;
670
704
  }
@@ -709,26 +743,10 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
709
743
  const functionToolCalls = require_helpers.requireEmittedFunctionToolCalls(response, "Ollama");
710
744
  const effectiveStrict = require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression;
711
745
  const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, logger);
712
- if (!streaming) {
713
- const body = buildOllamaChatContentWithToolCallsResponse(response.content ?? "", functionToolCalls, completionReq.model, effReasoning, response.blocks);
714
- res.writeHead(200, { "Content-Type": "application/json" });
715
- res.end(JSON.stringify(body));
716
- } else {
746
+ if (!streaming) await require_deliver.deliverJson(res, journalEntry, fixture, () => buildOllamaChatContentWithToolCallsResponse(response.content ?? "", functionToolCalls, completionReq.model, effReasoning, response.blocks), jsonOpts(require_timing.servedPayloadLength(response, effReasoning)));
747
+ else {
717
748
  const chunks = buildOllamaChatContentWithToolCallsChunks(response.content ?? "", functionToolCalls, completionReq.model, chunkSize, effReasoning, response.blocks);
718
- const interruption = require_interruption.createInterruptionSignal(fixture);
719
- if (!await require_ndjson_writer.writeNDJSONStream(res, chunks, {
720
- latency,
721
- streamingProfile: fixture.streamingProfile,
722
- recordedTimings: fixture.recordedTimings,
723
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
724
- signal: interruption?.signal,
725
- onChunkSent: interruption?.tick
726
- })) {
727
- if (!res.writableEnded) res.destroy();
728
- journalEntry.response.interrupted = true;
729
- journalEntry.response.interruptReason = interruption?.reason();
730
- }
731
- interruption?.cleanup();
749
+ await require_deliver.deliverStream(res, journalEntry, fixture, require_ndjson_writer.ndjsonCodec(), chunks, streamTiming);
732
750
  }
733
751
  return;
734
752
  }
@@ -752,26 +770,10 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
752
770
  });
753
771
  const effectiveStrict = require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression;
754
772
  const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, logger);
755
- if (!streaming) {
756
- const body = buildOllamaChatTextResponse(response.content, completionReq.model, effReasoning);
757
- res.writeHead(200, { "Content-Type": "application/json" });
758
- res.end(JSON.stringify(body));
759
- } else {
773
+ if (!streaming) await require_deliver.deliverJson(res, journalEntry, fixture, () => buildOllamaChatTextResponse(response.content, completionReq.model, effReasoning), jsonOpts(require_timing.servedPayloadLength(response, effReasoning)));
774
+ else {
760
775
  const chunks = buildOllamaChatTextChunks(response.content, completionReq.model, chunkSize, effReasoning);
761
- const interruption = require_interruption.createInterruptionSignal(fixture);
762
- if (!await require_ndjson_writer.writeNDJSONStream(res, chunks, {
763
- latency,
764
- streamingProfile: fixture.streamingProfile,
765
- recordedTimings: fixture.recordedTimings,
766
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
767
- signal: interruption?.signal,
768
- onChunkSent: interruption?.tick
769
- })) {
770
- if (!res.writableEnded) res.destroy();
771
- journalEntry.response.interrupted = true;
772
- journalEntry.response.interruptReason = interruption?.reason();
773
- }
774
- interruption?.cleanup();
776
+ await require_deliver.deliverStream(res, journalEntry, fixture, require_ndjson_writer.ndjsonCodec(), chunks, streamTiming);
775
777
  }
776
778
  return;
777
779
  }
@@ -796,26 +798,10 @@ async function handleOllama(req, res, raw, fixtures, journal, defaults, setCorsH
796
798
  const functionToolCalls = require_helpers.requireEmittedFunctionToolCalls(response, "Ollama");
797
799
  const effectiveStrict = require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression;
798
800
  const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, logger);
799
- if (!streaming) {
800
- const body = buildOllamaChatToolCallResponse(functionToolCalls, completionReq.model, effReasoning);
801
- res.writeHead(200, { "Content-Type": "application/json" });
802
- res.end(JSON.stringify(body));
803
- } else {
801
+ if (!streaming) await require_deliver.deliverJson(res, journalEntry, fixture, () => buildOllamaChatToolCallResponse(functionToolCalls, completionReq.model, effReasoning), jsonOpts(require_timing.servedPayloadLength(response, effReasoning)));
802
+ else {
804
803
  const chunks = buildOllamaChatToolCallChunks(functionToolCalls, completionReq.model, chunkSize, effReasoning);
805
- const interruption = require_interruption.createInterruptionSignal(fixture);
806
- if (!await require_ndjson_writer.writeNDJSONStream(res, chunks, {
807
- latency,
808
- streamingProfile: fixture.streamingProfile,
809
- recordedTimings: fixture.recordedTimings,
810
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
811
- signal: interruption?.signal,
812
- onChunkSent: interruption?.tick
813
- })) {
814
- if (!res.writableEnded) res.destroy();
815
- journalEntry.response.interrupted = true;
816
- journalEntry.response.interruptReason = interruption?.reason();
817
- }
818
- interruption?.cleanup();
804
+ await require_deliver.deliverStream(res, journalEntry, fixture, require_ndjson_writer.ndjsonCodec(), chunks, streamTiming);
819
805
  }
820
806
  return;
821
807
  }
@@ -901,7 +887,10 @@ async function handleOllamaGenerate(req, res, raw, fixtures, journal, defaults,
901
887
  completionReq._endpointType = "chat";
902
888
  completionReq._context = require_helpers.getContext(req);
903
889
  const testId = require_helpers.getTestId(req);
904
- const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, require_router.recordMatchOptions(!!defaults.record, defaults.logger));
890
+ const { fixture, skippedBySequenceOrTurn } = require_router.matchFixtureDiagnostic(fixtures, completionReq, journal.getFixtureMatchCountsForTest(testId), defaults.requestTransform, {
891
+ ...require_router.recordMatchOptions(!!defaults.record, defaults.logger),
892
+ wire: "ollama-generate"
893
+ });
905
894
  if (fixture) {
906
895
  journal.incrementFixtureMatchCount(fixture, fixtures, testId);
907
896
  defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
@@ -935,7 +924,7 @@ async function handleOllamaGenerate(req, res, raw, fixtures, journal, defaults,
935
924
  return;
936
925
  }
937
926
  if (defaults.record) {
938
- const outcome = await require_recorder.proxyAndRecord(req, res, completionReq, "ollama", urlPath, fixtures, defaults, raw);
927
+ const { outcome, clientHungUp } = await require_proxy.withHangUpDetection(req, res, () => require_recorder.proxyAndRecord(req, res, completionReq, "ollama", urlPath, fixtures, defaults, raw));
939
928
  if (outcome === "handled_by_hook") return;
940
929
  if (outcome !== "not_configured") {
941
930
  journal.add({
@@ -946,7 +935,8 @@ async function handleOllamaGenerate(req, res, raw, fixtures, journal, defaults,
946
935
  response: {
947
936
  status: res.statusCode ?? 200,
948
937
  fixture: null,
949
- source: "proxy"
938
+ source: "proxy",
939
+ ...require_proxy.proxyInterruption(res, clientHungUp)
950
940
  }
951
941
  });
952
942
  return;
@@ -1005,8 +995,40 @@ async function handleOllamaGenerate(req, res, raw, fixtures, journal, defaults,
1005
995
  }).body);
1006
996
  return;
1007
997
  }
998
+ if (require_helpers.isRawResponse(response)) {
999
+ await require_raw.serveRawResponse({
1000
+ wire: "ollama-generate",
1001
+ stream: generateReq.stream !== false,
1002
+ req,
1003
+ res,
1004
+ fixture,
1005
+ response,
1006
+ body: completionReq,
1007
+ journal,
1008
+ defaults,
1009
+ testId,
1010
+ misbehaviorSummary: misbehavior.summary,
1011
+ errorWire: "ollama"
1012
+ });
1013
+ return;
1014
+ }
1008
1015
  const latency = fixture.latency ?? defaults.latency;
1009
1016
  const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);
1017
+ const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;
1018
+ const streamTiming = {
1019
+ latency,
1020
+ streamingProfile: fixture.streamingProfile,
1021
+ recordedTimings: fixture.recordedTimings,
1022
+ replaySpeed
1023
+ };
1024
+ const jsonOpts = (payloadLength, duplicateId = false) => ({
1025
+ surface: "other",
1026
+ payloadLength,
1027
+ chunkSize,
1028
+ recordedTimings: fixture.recordedTimings,
1029
+ duplicateId,
1030
+ replaySpeed
1031
+ });
1010
1032
  const streaming = generateReq.stream !== false;
1011
1033
  if (require_helpers.isErrorResponse(response)) {
1012
1034
  const status = response.status ?? 500;
@@ -1047,26 +1069,10 @@ async function handleOllamaGenerate(req, res, raw, fixtures, journal, defaults,
1047
1069
  });
1048
1070
  const effectiveStrict = require_policy.resolveRequestDecisions(defaults.strict, req.headers).reasoningSuppression;
1049
1071
  const effReasoning = require_helpers.resolveReasoningForModel(response.reasoning, completionReq.model, effectiveStrict, defaults.logger);
1050
- if (!streaming) {
1051
- const body = buildOllamaGenerateTextResponse(response.content, completionReq.model, effReasoning);
1052
- res.writeHead(200, { "Content-Type": "application/json" });
1053
- res.end(JSON.stringify(body));
1054
- } else {
1072
+ if (!streaming) await require_deliver.deliverJson(res, journalEntry, fixture, () => buildOllamaGenerateTextResponse(response.content, completionReq.model, effReasoning), jsonOpts(require_timing.servedPayloadLength({ content: response.content }, effReasoning)));
1073
+ else {
1055
1074
  const chunks = buildOllamaGenerateTextChunks(response.content, completionReq.model, chunkSize, effReasoning);
1056
- const interruption = require_interruption.createInterruptionSignal(fixture);
1057
- if (!await require_ndjson_writer.writeNDJSONStream(res, chunks, {
1058
- latency,
1059
- streamingProfile: fixture.streamingProfile,
1060
- recordedTimings: fixture.recordedTimings,
1061
- replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
1062
- signal: interruption?.signal,
1063
- onChunkSent: interruption?.tick
1064
- })) {
1065
- if (!res.writableEnded) res.destroy();
1066
- journalEntry.response.interrupted = true;
1067
- journalEntry.response.interruptReason = interruption?.reason();
1068
- }
1069
- interruption?.cleanup();
1075
+ await require_deliver.deliverStream(res, journalEntry, fixture, require_ndjson_writer.ndjsonCodec(), chunks, streamTiming);
1070
1076
  }
1071
1077
  return;
1072
1078
  }