@copilotkit/aimock 1.24.1 → 1.26.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 (206) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +38 -0
  4. package/README.md +21 -11
  5. package/dist/agui-types.d.cts.map +1 -1
  6. package/dist/agui-types.d.ts.map +1 -1
  7. package/dist/aws-event-stream.cjs +2 -1
  8. package/dist/aws-event-stream.cjs.map +1 -1
  9. package/dist/aws-event-stream.d.cts +3 -1
  10. package/dist/aws-event-stream.d.cts.map +1 -1
  11. package/dist/aws-event-stream.d.ts +3 -1
  12. package/dist/aws-event-stream.d.ts.map +1 -1
  13. package/dist/aws-event-stream.js +2 -1
  14. package/dist/aws-event-stream.js.map +1 -1
  15. package/dist/bedrock-converse.cjs +8 -2
  16. package/dist/bedrock-converse.cjs.map +1 -1
  17. package/dist/bedrock-converse.d.cts.map +1 -1
  18. package/dist/bedrock-converse.d.ts.map +1 -1
  19. package/dist/bedrock-converse.js +8 -2
  20. package/dist/bedrock-converse.js.map +1 -1
  21. package/dist/bedrock.cjs +8 -2
  22. package/dist/bedrock.cjs.map +1 -1
  23. package/dist/bedrock.d.cts.map +1 -1
  24. package/dist/bedrock.d.ts.map +1 -1
  25. package/dist/bedrock.js +8 -2
  26. package/dist/bedrock.js.map +1 -1
  27. package/dist/cli.cjs +36 -1
  28. package/dist/cli.cjs.map +1 -1
  29. package/dist/cli.js +36 -1
  30. package/dist/cli.js.map +1 -1
  31. package/dist/cohere.cjs +206 -2
  32. package/dist/cohere.cjs.map +1 -1
  33. package/dist/cohere.d.cts.map +1 -1
  34. package/dist/cohere.d.ts.map +1 -1
  35. package/dist/cohere.js +207 -4
  36. package/dist/cohere.js.map +1 -1
  37. package/dist/config-loader.d.ts.map +1 -1
  38. package/dist/elevenlabs-audio.cjs +173 -1
  39. package/dist/elevenlabs-audio.cjs.map +1 -1
  40. package/dist/elevenlabs-audio.d.cts.map +1 -1
  41. package/dist/elevenlabs-audio.d.ts.map +1 -1
  42. package/dist/elevenlabs-audio.js +173 -2
  43. package/dist/elevenlabs-audio.js.map +1 -1
  44. package/dist/embeddings.cjs +1 -1
  45. package/dist/embeddings.cjs.map +1 -1
  46. package/dist/embeddings.js +1 -1
  47. package/dist/embeddings.js.map +1 -1
  48. package/dist/fal-audio.cjs +2 -4
  49. package/dist/fal-audio.cjs.map +1 -1
  50. package/dist/fal-audio.js +2 -4
  51. package/dist/fal-audio.js.map +1 -1
  52. package/dist/fal.cjs +2 -2
  53. package/dist/fal.cjs.map +1 -1
  54. package/dist/fal.d.cts.map +1 -1
  55. package/dist/fal.d.ts.map +1 -1
  56. package/dist/fal.js +2 -2
  57. package/dist/fal.js.map +1 -1
  58. package/dist/fixture-loader.cjs +16 -3
  59. package/dist/fixture-loader.cjs.map +1 -1
  60. package/dist/fixture-loader.d.cts.map +1 -1
  61. package/dist/fixture-loader.d.ts.map +1 -1
  62. package/dist/fixture-loader.js +16 -3
  63. package/dist/fixture-loader.js.map +1 -1
  64. package/dist/gemini-embeddings.cjs +166 -0
  65. package/dist/gemini-embeddings.cjs.map +1 -0
  66. package/dist/gemini-embeddings.js +166 -0
  67. package/dist/gemini-embeddings.js.map +1 -0
  68. package/dist/gemini-interactions.cjs +10 -2
  69. package/dist/gemini-interactions.cjs.map +1 -1
  70. package/dist/gemini-interactions.d.cts.map +1 -1
  71. package/dist/gemini-interactions.d.ts.map +1 -1
  72. package/dist/gemini-interactions.js +10 -2
  73. package/dist/gemini-interactions.js.map +1 -1
  74. package/dist/gemini.cjs +12 -2
  75. package/dist/gemini.cjs.map +1 -1
  76. package/dist/gemini.d.cts.map +1 -1
  77. package/dist/gemini.d.ts.map +1 -1
  78. package/dist/gemini.js +12 -2
  79. package/dist/gemini.js.map +1 -1
  80. package/dist/helpers.cjs +70 -33
  81. package/dist/helpers.cjs.map +1 -1
  82. package/dist/helpers.d.cts +9 -5
  83. package/dist/helpers.d.cts.map +1 -1
  84. package/dist/helpers.d.ts +9 -5
  85. package/dist/helpers.d.ts.map +1 -1
  86. package/dist/helpers.js +68 -34
  87. package/dist/helpers.js.map +1 -1
  88. package/dist/images.cjs +295 -13
  89. package/dist/images.cjs.map +1 -1
  90. package/dist/images.d.cts +9 -1
  91. package/dist/images.d.cts.map +1 -1
  92. package/dist/images.d.ts +9 -1
  93. package/dist/images.d.ts.map +1 -1
  94. package/dist/images.js +294 -14
  95. package/dist/images.js.map +1 -1
  96. package/dist/index.cjs +1 -1
  97. package/dist/index.d.cts +2 -2
  98. package/dist/index.d.ts +2 -2
  99. package/dist/index.js +1 -1
  100. package/dist/llmock.cjs +16 -1
  101. package/dist/llmock.cjs.map +1 -1
  102. package/dist/llmock.d.cts +2 -0
  103. package/dist/llmock.d.cts.map +1 -1
  104. package/dist/llmock.d.ts +2 -0
  105. package/dist/llmock.d.ts.map +1 -1
  106. package/dist/llmock.js +16 -1
  107. package/dist/llmock.js.map +1 -1
  108. package/dist/messages.cjs +9 -2
  109. package/dist/messages.cjs.map +1 -1
  110. package/dist/messages.d.cts.map +1 -1
  111. package/dist/messages.d.ts.map +1 -1
  112. package/dist/messages.js +9 -2
  113. package/dist/messages.js.map +1 -1
  114. package/dist/metrics.cjs +2 -0
  115. package/dist/metrics.cjs.map +1 -1
  116. package/dist/metrics.d.cts.map +1 -1
  117. package/dist/metrics.d.ts.map +1 -1
  118. package/dist/metrics.js +2 -0
  119. package/dist/metrics.js.map +1 -1
  120. package/dist/ndjson-writer.cjs +2 -1
  121. package/dist/ndjson-writer.cjs.map +1 -1
  122. package/dist/ndjson-writer.d.cts +3 -2
  123. package/dist/ndjson-writer.d.cts.map +1 -1
  124. package/dist/ndjson-writer.d.ts +3 -2
  125. package/dist/ndjson-writer.d.ts.map +1 -1
  126. package/dist/ndjson-writer.js +2 -1
  127. package/dist/ndjson-writer.js.map +1 -1
  128. package/dist/ollama.cjs +197 -2
  129. package/dist/ollama.cjs.map +1 -1
  130. package/dist/ollama.d.cts.map +1 -1
  131. package/dist/ollama.d.ts.map +1 -1
  132. package/dist/ollama.js +198 -4
  133. package/dist/ollama.js.map +1 -1
  134. package/dist/recorder.cjs +49 -5
  135. package/dist/recorder.cjs.map +1 -1
  136. package/dist/recorder.d.cts.map +1 -1
  137. package/dist/recorder.d.ts.map +1 -1
  138. package/dist/recorder.js +49 -5
  139. package/dist/recorder.js.map +1 -1
  140. package/dist/responses.cjs +11 -2
  141. package/dist/responses.cjs.map +1 -1
  142. package/dist/responses.d.cts.map +1 -1
  143. package/dist/responses.d.ts.map +1 -1
  144. package/dist/responses.js +11 -2
  145. package/dist/responses.js.map +1 -1
  146. package/dist/server.cjs +196 -49
  147. package/dist/server.cjs.map +1 -1
  148. package/dist/server.d.cts.map +1 -1
  149. package/dist/server.d.ts.map +1 -1
  150. package/dist/server.js +201 -54
  151. package/dist/server.js.map +1 -1
  152. package/dist/speech.cjs +1 -1
  153. package/dist/speech.cjs.map +1 -1
  154. package/dist/speech.js +1 -1
  155. package/dist/speech.js.map +1 -1
  156. package/dist/sse-writer.cjs +48 -10
  157. package/dist/sse-writer.cjs.map +1 -1
  158. package/dist/sse-writer.d.cts +12 -4
  159. package/dist/sse-writer.d.cts.map +1 -1
  160. package/dist/sse-writer.d.ts +12 -4
  161. package/dist/sse-writer.d.ts.map +1 -1
  162. package/dist/sse-writer.js +48 -10
  163. package/dist/sse-writer.js.map +1 -1
  164. package/dist/transcription.cjs +9 -6
  165. package/dist/transcription.cjs.map +1 -1
  166. package/dist/transcription.d.cts +2 -2
  167. package/dist/transcription.d.cts.map +1 -1
  168. package/dist/transcription.d.ts +2 -2
  169. package/dist/transcription.d.ts.map +1 -1
  170. package/dist/transcription.js +8 -7
  171. package/dist/transcription.js.map +1 -1
  172. package/dist/types.d.cts +45 -3
  173. package/dist/types.d.cts.map +1 -1
  174. package/dist/types.d.ts +45 -3
  175. package/dist/types.d.ts.map +1 -1
  176. package/dist/video.cjs +1 -1
  177. package/dist/video.cjs.map +1 -1
  178. package/dist/video.d.cts.map +1 -1
  179. package/dist/video.d.ts.map +1 -1
  180. package/dist/video.js +1 -1
  181. package/dist/video.js.map +1 -1
  182. package/dist/ws-gemini-live.cjs +14 -4
  183. package/dist/ws-gemini-live.cjs.map +1 -1
  184. package/dist/ws-gemini-live.d.cts +1 -0
  185. package/dist/ws-gemini-live.d.cts.map +1 -1
  186. package/dist/ws-gemini-live.d.ts +1 -0
  187. package/dist/ws-gemini-live.d.ts.map +1 -1
  188. package/dist/ws-gemini-live.js +15 -5
  189. package/dist/ws-gemini-live.js.map +1 -1
  190. package/dist/ws-realtime.cjs +21 -4
  191. package/dist/ws-realtime.cjs.map +1 -1
  192. package/dist/ws-realtime.d.cts +1 -0
  193. package/dist/ws-realtime.d.cts.map +1 -1
  194. package/dist/ws-realtime.d.ts +1 -0
  195. package/dist/ws-realtime.d.ts.map +1 -1
  196. package/dist/ws-realtime.js +22 -5
  197. package/dist/ws-realtime.js.map +1 -1
  198. package/dist/ws-responses.cjs +8 -5
  199. package/dist/ws-responses.cjs.map +1 -1
  200. package/dist/ws-responses.d.cts +1 -0
  201. package/dist/ws-responses.d.cts.map +1 -1
  202. package/dist/ws-responses.d.ts +1 -0
  203. package/dist/ws-responses.d.ts.map +1 -1
  204. package/dist/ws-responses.js +9 -6
  205. package/dist/ws-responses.js.map +1 -1
  206. package/package.json +2 -2
package/dist/messages.js CHANGED
@@ -472,6 +472,7 @@ async function writeClaudeSSEStream(res, events, optionsOrLatency) {
472
472
  const opts = typeof optionsOrLatency === "number" ? { latency: optionsOrLatency } : optionsOrLatency ?? {};
473
473
  const latency = opts.latency ?? 0;
474
474
  const profile = opts.streamingProfile;
475
+ const { recordedTimings, replaySpeed } = opts;
475
476
  const signal = opts.signal;
476
477
  const onChunkSent = opts.onChunkSent;
477
478
  if (res.writableEnded) return true;
@@ -480,7 +481,7 @@ async function writeClaudeSSEStream(res, events, optionsOrLatency) {
480
481
  res.setHeader("Connection", "keep-alive");
481
482
  let chunkIndex = 0;
482
483
  for (const event of events) {
483
- const chunkDelay = calculateDelay(chunkIndex, profile, latency);
484
+ const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);
484
485
  if (chunkDelay > 0) await delay(chunkDelay, signal);
485
486
  if (signal?.aborted) return false;
486
487
  if (res.writableEnded) return true;
@@ -612,7 +613,7 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
612
613
  message: response.error.message
613
614
  }
614
615
  };
615
- writeErrorResponse(res, status, JSON.stringify(anthropicError));
616
+ writeErrorResponse(res, status, JSON.stringify(anthropicError), { retryAfter: response.retryAfter });
616
617
  return;
617
618
  }
618
619
  if (isContentWithToolCallsResponse(response)) {
@@ -638,6 +639,8 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
638
639
  if (!await writeClaudeSSEStream(res, events, {
639
640
  latency,
640
641
  streamingProfile: fixture.streamingProfile,
642
+ recordedTimings: fixture.recordedTimings,
643
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
641
644
  signal: interruption?.signal,
642
645
  onChunkSent: interruption?.tick
643
646
  })) {
@@ -672,6 +675,8 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
672
675
  if (!await writeClaudeSSEStream(res, events, {
673
676
  latency,
674
677
  streamingProfile: fixture.streamingProfile,
678
+ recordedTimings: fixture.recordedTimings,
679
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
675
680
  signal: interruption?.signal,
676
681
  onChunkSent: interruption?.tick
677
682
  })) {
@@ -706,6 +711,8 @@ async function handleMessages(req, res, raw, fixtures, journal, defaults, setCor
706
711
  if (!await writeClaudeSSEStream(res, events, {
707
712
  latency,
708
713
  streamingProfile: fixture.streamingProfile,
714
+ recordedTimings: fixture.recordedTimings,
715
+ replaySpeed: fixture.replaySpeed ?? defaults.replaySpeed,
709
716
  signal: interruption?.signal,
710
717
  onChunkSent: interruption?.tick
711
718
  })) {
@@ -1 +1 @@
1
- {"version":3,"file":"messages.js","names":[],"sources":["../src/messages.ts"],"sourcesContent":["/**\n * Anthropic Claude Messages API support.\n *\n * Translates incoming /v1/messages requests into the ChatCompletionRequest\n * format used by the fixture router, and converts fixture responses back into\n * the Claude Messages API streaming (or non-streaming) format.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n ResponseOverrides,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n generateMessageId,\n generateToolUseId,\n extractOverrides,\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse, delay, calculateDelay } from \"./sse-writer.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Claude Messages API request types ──────────────────────────────────────\n\ninterface ClaudeContentBlock {\n type: \"text\" | \"tool_use\" | \"tool_result\" | \"image\" | \"document\";\n text?: string;\n id?: string;\n name?: string;\n input?: unknown;\n tool_use_id?: string;\n content?: string | ClaudeContentBlock[];\n is_error?: boolean;\n}\n\ninterface ClaudeMessage {\n role: \"user\" | \"assistant\";\n content: string | ClaudeContentBlock[];\n}\n\ninterface ClaudeToolDef {\n name: string;\n description?: string;\n input_schema?: object;\n}\n\ninterface ClaudeRequest {\n model: string;\n messages: ClaudeMessage[];\n system?: string | ClaudeContentBlock[];\n tools?: ClaudeToolDef[];\n tool_choice?: unknown;\n stream?: boolean;\n max_tokens: number;\n temperature?: number;\n [key: string]: unknown;\n}\n\n// ─── Input conversion: Claude → ChatCompletions messages ────────────────────\n\nfunction extractClaudeTextContent(content: string | ClaudeContentBlock[]): string {\n if (typeof content === \"string\") return content;\n return content\n .filter((b) => b.type === \"text\")\n .map((b) => b.text ?? \"\")\n .join(\"\");\n}\n\nexport function claudeToCompletionRequest(req: ClaudeRequest): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n // system field → system message\n if (req.system) {\n const systemText =\n typeof req.system === \"string\"\n ? req.system\n : req.system\n .filter((b) => b.type === \"text\")\n .map((b) => b.text ?? \"\")\n .join(\"\");\n if (systemText) {\n messages.push({ role: \"system\", content: systemText });\n }\n }\n\n for (const msg of req.messages) {\n if (msg.role === \"user\") {\n // Check for tool_result blocks\n if (typeof msg.content !== \"string\" && Array.isArray(msg.content)) {\n const toolResults = msg.content.filter((b) => b.type === \"tool_result\");\n const textBlocks = msg.content.filter((b) => b.type === \"text\");\n\n if (toolResults.length > 0) {\n // Each tool_result → tool message\n for (const tr of toolResults) {\n const resultContent =\n typeof tr.content === \"string\"\n ? tr.content\n : Array.isArray(tr.content)\n ? tr.content\n .filter((b) => b.type === \"text\")\n .map((b) => b.text ?? \"\")\n .join(\"\")\n : \"\";\n messages.push({\n role: \"tool\",\n content: resultContent,\n tool_call_id: tr.tool_use_id,\n });\n }\n // Any accompanying text blocks → user message\n if (textBlocks.length > 0) {\n messages.push({\n role: \"user\",\n content: textBlocks.map((b) => b.text ?? \"\").join(\"\"),\n });\n }\n continue;\n }\n }\n // Regular user message\n messages.push({\n role: \"user\",\n content: extractClaudeTextContent(msg.content),\n });\n } else if (msg.role === \"assistant\") {\n if (typeof msg.content === \"string\") {\n messages.push({ role: \"assistant\", content: msg.content });\n } else if (Array.isArray(msg.content)) {\n const toolUseBlocks = msg.content.filter((b) => b.type === \"tool_use\");\n const textContent = extractClaudeTextContent(msg.content);\n\n if (toolUseBlocks.length > 0) {\n messages.push({\n role: \"assistant\",\n content: textContent || null,\n tool_calls: toolUseBlocks.map((b) => ({\n id: b.id ?? generateToolUseId(),\n type: \"function\" as const,\n function: {\n name: b.name ?? \"\",\n arguments: typeof b.input === \"string\" ? b.input : JSON.stringify(b.input ?? {}),\n },\n })),\n });\n } else {\n messages.push({ role: \"assistant\", content: textContent || null });\n }\n } else {\n // null/undefined content — tool-only assistant turn\n messages.push({ role: \"assistant\", content: null });\n }\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n tools = req.tools.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.input_schema,\n },\n }));\n }\n\n return {\n model: req.model,\n messages,\n stream: req.stream,\n temperature: req.temperature,\n max_tokens: req.max_tokens,\n tools,\n _endpointType: \"chat\",\n };\n}\n\n// ─── Response building: fixture → Claude Messages API format ────────────────\n\nfunction claudeStopReason(finishReason: string | undefined, defaultReason: string): string {\n if (!finishReason) return defaultReason;\n if (finishReason === \"stop\") return \"end_turn\";\n if (finishReason === \"tool_calls\") return \"tool_use\";\n if (finishReason === \"length\") return \"max_tokens\";\n return finishReason;\n}\n\nfunction claudeUsage(overrides?: ResponseOverrides): {\n input_tokens: number;\n output_tokens: number;\n} {\n if (!overrides?.usage) return { input_tokens: 0, output_tokens: 0 };\n return {\n input_tokens: overrides.usage.input_tokens ?? overrides.usage.prompt_tokens ?? 0,\n output_tokens: overrides.usage.output_tokens ?? overrides.usage.completion_tokens ?? 0,\n };\n}\n\ninterface ClaudeSSEEvent {\n type: string;\n [key: string]: unknown;\n}\n\nfunction buildClaudeTextStreamEvents(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): ClaudeSSEEvent[] {\n const msgId = overrides?.id ?? generateMessageId();\n const effectiveModel = overrides?.model ?? model;\n const events: ClaudeSSEEvent[] = [];\n\n // message_start\n events.push({\n type: \"message_start\",\n message: {\n id: msgId,\n type: \"message\",\n role: overrides?.role ?? \"assistant\",\n content: [],\n model: effectiveModel,\n stop_reason: null,\n stop_sequence: null,\n usage: claudeUsage(overrides),\n },\n });\n\n let blockIndex = 0;\n\n // Thinking block (emitted before text when reasoning is present)\n if (reasoning) {\n events.push({\n type: \"content_block_start\",\n index: blockIndex,\n content_block: { type: \"thinking\", thinking: \"\", signature: \"\" },\n });\n\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n events.push({\n type: \"content_block_delta\",\n index: blockIndex,\n delta: { type: \"thinking_delta\", thinking: slice },\n });\n }\n\n events.push({\n type: \"content_block_delta\",\n index: blockIndex,\n delta: { type: \"signature_delta\", signature: \"\" },\n });\n\n events.push({\n type: \"content_block_stop\",\n index: blockIndex,\n });\n\n blockIndex++;\n }\n\n // content_block_start (text)\n events.push({\n type: \"content_block_start\",\n index: blockIndex,\n content_block: { type: \"text\", text: \"\" },\n });\n\n // content_block_delta — text chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n type: \"content_block_delta\",\n index: blockIndex,\n delta: { type: \"text_delta\", text: slice },\n });\n }\n\n // content_block_stop\n events.push({\n type: \"content_block_stop\",\n index: blockIndex,\n });\n\n // message_delta\n events.push({\n type: \"message_delta\",\n delta: {\n stop_reason: claudeStopReason(overrides?.finishReason, \"end_turn\"),\n stop_sequence: null,\n },\n usage: { output_tokens: claudeUsage(overrides).output_tokens },\n });\n\n // message_stop\n events.push({ type: \"message_stop\" });\n\n return events;\n}\n\nfunction buildClaudeToolCallStreamEvents(\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n logger: Logger,\n overrides?: ResponseOverrides,\n): ClaudeSSEEvent[] {\n const msgId = overrides?.id ?? generateMessageId();\n const effectiveModel = overrides?.model ?? model;\n const events: ClaudeSSEEvent[] = [];\n\n // message_start\n events.push({\n type: \"message_start\",\n message: {\n id: msgId,\n type: \"message\",\n role: overrides?.role ?? \"assistant\",\n content: [],\n model: effectiveModel,\n stop_reason: null,\n stop_sequence: null,\n usage: claudeUsage(overrides),\n },\n });\n\n for (let idx = 0; idx < toolCalls.length; idx++) {\n const tc = toolCalls[idx];\n const toolUseId = tc.id || generateToolUseId();\n\n // Parse arguments to JSON object (Claude uses objects, not strings)\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 const argsJson = JSON.stringify(argsObj);\n\n // content_block_start\n events.push({\n type: \"content_block_start\",\n index: idx,\n content_block: {\n type: \"tool_use\",\n id: toolUseId,\n name: tc.name,\n input: {},\n },\n });\n\n // content_block_delta — input_json_delta chunks\n for (let i = 0; i < argsJson.length; i += chunkSize) {\n const slice = argsJson.slice(i, i + chunkSize);\n events.push({\n type: \"content_block_delta\",\n index: idx,\n delta: { type: \"input_json_delta\", partial_json: slice },\n });\n }\n\n // content_block_stop\n events.push({\n type: \"content_block_stop\",\n index: idx,\n });\n }\n\n // message_delta\n events.push({\n type: \"message_delta\",\n delta: {\n stop_reason: claudeStopReason(overrides?.finishReason, \"tool_use\"),\n stop_sequence: null,\n },\n usage: { output_tokens: claudeUsage(overrides).output_tokens },\n });\n\n // message_stop\n events.push({ type: \"message_stop\" });\n\n return events;\n}\n\n// Non-streaming response builders\n\nfunction buildClaudeTextResponse(\n content: string,\n model: string,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): object {\n const contentBlocks: object[] = [];\n\n if (reasoning) {\n contentBlocks.push({ type: \"thinking\", thinking: reasoning, signature: \"\" });\n }\n\n contentBlocks.push({ type: \"text\", text: content });\n\n return {\n id: overrides?.id ?? generateMessageId(),\n type: \"message\",\n role: overrides?.role ?? \"assistant\",\n content: contentBlocks,\n model: overrides?.model ?? model,\n stop_reason: claudeStopReason(overrides?.finishReason, \"end_turn\"),\n stop_sequence: null,\n usage: claudeUsage(overrides),\n };\n}\n\nfunction buildClaudeToolCallResponse(\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): object {\n return {\n id: overrides?.id ?? generateMessageId(),\n type: \"message\",\n role: overrides?.role ?? \"assistant\",\n content: 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 type: \"tool_use\",\n id: tc.id || generateToolUseId(),\n name: tc.name,\n input: argsObj,\n };\n }),\n model: overrides?.model ?? model,\n stop_reason: claudeStopReason(overrides?.finishReason, \"tool_use\"),\n stop_sequence: null,\n usage: claudeUsage(overrides),\n };\n}\n\nfunction buildClaudeContentWithToolCallsStreamEvents(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): ClaudeSSEEvent[] {\n const msgId = overrides?.id ?? generateMessageId();\n const effectiveModel = overrides?.model ?? model;\n const events: ClaudeSSEEvent[] = [];\n\n // message_start\n events.push({\n type: \"message_start\",\n message: {\n id: msgId,\n type: \"message\",\n role: overrides?.role ?? \"assistant\",\n content: [],\n model: effectiveModel,\n stop_reason: null,\n stop_sequence: null,\n usage: claudeUsage(overrides),\n },\n });\n\n let blockIndex = 0;\n\n // Optional thinking block\n if (reasoning) {\n events.push({\n type: \"content_block_start\",\n index: blockIndex,\n content_block: { type: \"thinking\", thinking: \"\", signature: \"\" },\n });\n\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n events.push({\n type: \"content_block_delta\",\n index: blockIndex,\n delta: { type: \"thinking_delta\", thinking: slice },\n });\n }\n\n events.push({\n type: \"content_block_delta\",\n index: blockIndex,\n delta: { type: \"signature_delta\", signature: \"\" },\n });\n\n events.push({\n type: \"content_block_stop\",\n index: blockIndex,\n });\n\n blockIndex++;\n }\n\n // Text content block\n events.push({\n type: \"content_block_start\",\n index: blockIndex,\n content_block: { type: \"text\", text: \"\" },\n });\n\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n type: \"content_block_delta\",\n index: blockIndex,\n delta: { type: \"text_delta\", text: slice },\n });\n }\n\n events.push({\n type: \"content_block_stop\",\n index: blockIndex,\n });\n\n blockIndex++;\n\n // Tool use blocks\n for (const tc of toolCalls) {\n const toolUseId = tc.id || generateToolUseId();\n\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 const argsJson = JSON.stringify(argsObj);\n\n events.push({\n type: \"content_block_start\",\n index: blockIndex,\n content_block: {\n type: \"tool_use\",\n id: toolUseId,\n name: tc.name,\n input: {},\n },\n });\n\n for (let i = 0; i < argsJson.length; i += chunkSize) {\n const slice = argsJson.slice(i, i + chunkSize);\n events.push({\n type: \"content_block_delta\",\n index: blockIndex,\n delta: { type: \"input_json_delta\", partial_json: slice },\n });\n }\n\n events.push({\n type: \"content_block_stop\",\n index: blockIndex,\n });\n\n blockIndex++;\n }\n\n // message_delta\n events.push({\n type: \"message_delta\",\n delta: {\n stop_reason: claudeStopReason(overrides?.finishReason, \"tool_use\"),\n stop_sequence: null,\n },\n usage: { output_tokens: claudeUsage(overrides).output_tokens },\n });\n\n // message_stop\n events.push({ type: \"message_stop\" });\n\n return events;\n}\n\nfunction buildClaudeContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): object {\n const contentBlocks: object[] = [];\n\n if (reasoning) {\n contentBlocks.push({ type: \"thinking\", thinking: reasoning, signature: \"\" });\n }\n\n contentBlocks.push({ type: \"text\", text: content });\n\n for (const tc of toolCalls) {\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 contentBlocks.push({\n type: \"tool_use\",\n id: tc.id || generateToolUseId(),\n name: tc.name,\n input: argsObj,\n });\n }\n\n return {\n id: overrides?.id ?? generateMessageId(),\n type: \"message\",\n role: overrides?.role ?? \"assistant\",\n content: contentBlocks,\n model: overrides?.model ?? model,\n stop_reason: claudeStopReason(overrides?.finishReason, \"tool_use\"),\n stop_sequence: null,\n usage: claudeUsage(overrides),\n };\n}\n\n// ─── SSE writer for Claude Messages API ─────────────────────────────────────\n\ninterface ClaudeStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nasync function writeClaudeSSEStream(\n res: http.ServerResponse,\n events: ClaudeSSEEvent[],\n optionsOrLatency?: number | ClaudeStreamOptions,\n): Promise<boolean> {\n const opts: ClaudeStreamOptions =\n typeof optionsOrLatency === \"number\" ? { latency: optionsOrLatency } : (optionsOrLatency ?? {});\n const latency = opts.latency ?? 0;\n const profile = opts.streamingProfile;\n const signal = opts.signal;\n const onChunkSent = opts.onChunkSent;\n\n if (res.writableEnded) return true;\n res.setHeader(\"Content-Type\", \"text/event-stream\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"keep-alive\");\n\n let chunkIndex = 0;\n for (const event of events) {\n const chunkDelay = calculateDelay(chunkIndex, profile, latency);\n if (chunkDelay > 0) await delay(chunkDelay, signal);\n if (signal?.aborted) return false;\n if (res.writableEnded) return true;\n res.write(`event: ${event.type}\\ndata: ${JSON.stringify(event)}\\n\\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\n// ─── Request handler ────────────────────────────────────────────────────────\n\nexport async function handleMessages(\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 let claudeReq: ClaudeRequest;\n try {\n claudeReq = JSON.parse(raw) as ClaudeRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/messages\",\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: ${detail}`,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = claudeToCompletionRequest(claudeReq);\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n const lastUserMsg = completionReq.messages.filter((m) => m.role === \"user\").pop();\n const snippet =\n typeof lastUserMsg?.content === \"string\" ? lastUserMsg.content.slice(0, 80) : \"\";\n logger.debug(\n `No fixture matched for request (model=${completionReq.model ?? \"?\"}, msg=\"${snippet}\")`,\n );\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/messages\",\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 = \"Strict mode: no fixture matched\";\n logger.error(\n `STRICT: No fixture matched for ${req.method ?? \"POST\"} ${req.url ?? \"/v1/messages\"}`,\n );\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/messages\",\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 \"anthropic\",\n req.url ?? \"/v1/messages\",\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: req.url ?? \"/v1/messages\",\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: req.url ?? \"/v1/messages\",\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 // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/messages\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n // Anthropic-style error format: { type: \"error\", error: { type, message } }\n const anthropicError = {\n type: \"error\",\n error: {\n type: response.error.type ?? \"api_error\",\n message: response.error.message,\n },\n };\n writeErrorResponse(res, status, JSON.stringify(anthropicError));\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(\n \"webSearches in fixture response are not supported for Claude Messages API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/messages\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (claudeReq.stream !== true) {\n const body = buildClaudeContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n completionReq.model,\n logger,\n response.reasoning,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildClaudeContentWithToolCallsStreamEvents(\n response.content,\n response.toolCalls,\n completionReq.model,\n chunkSize,\n logger,\n response.reasoning,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeClaudeSSEStream(res, events, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\n \"webSearches in fixture response are not supported for Claude Messages API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/messages\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (claudeReq.stream !== true) {\n const body = buildClaudeTextResponse(\n response.content,\n completionReq.model,\n response.reasoning,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildClaudeTextStreamEvents(\n response.content,\n completionReq.model,\n chunkSize,\n response.reasoning,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeClaudeSSEStream(res, events, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\n \"webSearches in fixture response are not supported for Claude Messages API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/messages\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (claudeReq.stream !== true) {\n const body = buildClaudeToolCallResponse(\n response.toolCalls,\n completionReq.model,\n logger,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildClaudeToolCallStreamEvents(\n response.toolCalls,\n completionReq.model,\n chunkSize,\n logger,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeClaudeSSEStream(res, events, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/messages\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n type: \"server_error\",\n },\n }),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"messages.js","names":[],"sources":["../src/messages.ts"],"sourcesContent":["/**\n * Anthropic Claude Messages API support.\n *\n * Translates incoming /v1/messages requests into the ChatCompletionRequest\n * format used by the fixture router, and converts fixture responses back into\n * the Claude Messages API streaming (or non-streaming) format.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n RecordedTimings,\n ResponseOverrides,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n generateMessageId,\n generateToolUseId,\n extractOverrides,\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse, delay, calculateDelay } from \"./sse-writer.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Claude Messages API request types ──────────────────────────────────────\n\ninterface ClaudeContentBlock {\n type: \"text\" | \"tool_use\" | \"tool_result\" | \"image\" | \"document\";\n text?: string;\n id?: string;\n name?: string;\n input?: unknown;\n tool_use_id?: string;\n content?: string | ClaudeContentBlock[];\n is_error?: boolean;\n}\n\ninterface ClaudeMessage {\n role: \"user\" | \"assistant\";\n content: string | ClaudeContentBlock[];\n}\n\ninterface ClaudeToolDef {\n name: string;\n description?: string;\n input_schema?: object;\n}\n\ninterface ClaudeRequest {\n model: string;\n messages: ClaudeMessage[];\n system?: string | ClaudeContentBlock[];\n tools?: ClaudeToolDef[];\n tool_choice?: unknown;\n stream?: boolean;\n max_tokens: number;\n temperature?: number;\n [key: string]: unknown;\n}\n\n// ─── Input conversion: Claude → ChatCompletions messages ────────────────────\n\nfunction extractClaudeTextContent(content: string | ClaudeContentBlock[]): string {\n if (typeof content === \"string\") return content;\n return content\n .filter((b) => b.type === \"text\")\n .map((b) => b.text ?? \"\")\n .join(\"\");\n}\n\nexport function claudeToCompletionRequest(req: ClaudeRequest): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n // system field → system message\n if (req.system) {\n const systemText =\n typeof req.system === \"string\"\n ? req.system\n : req.system\n .filter((b) => b.type === \"text\")\n .map((b) => b.text ?? \"\")\n .join(\"\");\n if (systemText) {\n messages.push({ role: \"system\", content: systemText });\n }\n }\n\n for (const msg of req.messages) {\n if (msg.role === \"user\") {\n // Check for tool_result blocks\n if (typeof msg.content !== \"string\" && Array.isArray(msg.content)) {\n const toolResults = msg.content.filter((b) => b.type === \"tool_result\");\n const textBlocks = msg.content.filter((b) => b.type === \"text\");\n\n if (toolResults.length > 0) {\n // Each tool_result → tool message\n for (const tr of toolResults) {\n const resultContent =\n typeof tr.content === \"string\"\n ? tr.content\n : Array.isArray(tr.content)\n ? tr.content\n .filter((b) => b.type === \"text\")\n .map((b) => b.text ?? \"\")\n .join(\"\")\n : \"\";\n messages.push({\n role: \"tool\",\n content: resultContent,\n tool_call_id: tr.tool_use_id,\n });\n }\n // Any accompanying text blocks → user message\n if (textBlocks.length > 0) {\n messages.push({\n role: \"user\",\n content: textBlocks.map((b) => b.text ?? \"\").join(\"\"),\n });\n }\n continue;\n }\n }\n // Regular user message\n messages.push({\n role: \"user\",\n content: extractClaudeTextContent(msg.content),\n });\n } else if (msg.role === \"assistant\") {\n if (typeof msg.content === \"string\") {\n messages.push({ role: \"assistant\", content: msg.content });\n } else if (Array.isArray(msg.content)) {\n const toolUseBlocks = msg.content.filter((b) => b.type === \"tool_use\");\n const textContent = extractClaudeTextContent(msg.content);\n\n if (toolUseBlocks.length > 0) {\n messages.push({\n role: \"assistant\",\n content: textContent || null,\n tool_calls: toolUseBlocks.map((b) => ({\n id: b.id ?? generateToolUseId(),\n type: \"function\" as const,\n function: {\n name: b.name ?? \"\",\n arguments: typeof b.input === \"string\" ? b.input : JSON.stringify(b.input ?? {}),\n },\n })),\n });\n } else {\n messages.push({ role: \"assistant\", content: textContent || null });\n }\n } else {\n // null/undefined content — tool-only assistant turn\n messages.push({ role: \"assistant\", content: null });\n }\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n tools = req.tools.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.input_schema,\n },\n }));\n }\n\n return {\n model: req.model,\n messages,\n stream: req.stream,\n temperature: req.temperature,\n max_tokens: req.max_tokens,\n tools,\n _endpointType: \"chat\",\n };\n}\n\n// ─── Response building: fixture → Claude Messages API format ────────────────\n\nfunction claudeStopReason(finishReason: string | undefined, defaultReason: string): string {\n if (!finishReason) return defaultReason;\n if (finishReason === \"stop\") return \"end_turn\";\n if (finishReason === \"tool_calls\") return \"tool_use\";\n if (finishReason === \"length\") return \"max_tokens\";\n return finishReason;\n}\n\nfunction claudeUsage(overrides?: ResponseOverrides): {\n input_tokens: number;\n output_tokens: number;\n} {\n if (!overrides?.usage) return { input_tokens: 0, output_tokens: 0 };\n return {\n input_tokens: overrides.usage.input_tokens ?? overrides.usage.prompt_tokens ?? 0,\n output_tokens: overrides.usage.output_tokens ?? overrides.usage.completion_tokens ?? 0,\n };\n}\n\ninterface ClaudeSSEEvent {\n type: string;\n [key: string]: unknown;\n}\n\nfunction buildClaudeTextStreamEvents(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): ClaudeSSEEvent[] {\n const msgId = overrides?.id ?? generateMessageId();\n const effectiveModel = overrides?.model ?? model;\n const events: ClaudeSSEEvent[] = [];\n\n // message_start\n events.push({\n type: \"message_start\",\n message: {\n id: msgId,\n type: \"message\",\n role: overrides?.role ?? \"assistant\",\n content: [],\n model: effectiveModel,\n stop_reason: null,\n stop_sequence: null,\n usage: claudeUsage(overrides),\n },\n });\n\n let blockIndex = 0;\n\n // Thinking block (emitted before text when reasoning is present)\n if (reasoning) {\n events.push({\n type: \"content_block_start\",\n index: blockIndex,\n content_block: { type: \"thinking\", thinking: \"\", signature: \"\" },\n });\n\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n events.push({\n type: \"content_block_delta\",\n index: blockIndex,\n delta: { type: \"thinking_delta\", thinking: slice },\n });\n }\n\n events.push({\n type: \"content_block_delta\",\n index: blockIndex,\n delta: { type: \"signature_delta\", signature: \"\" },\n });\n\n events.push({\n type: \"content_block_stop\",\n index: blockIndex,\n });\n\n blockIndex++;\n }\n\n // content_block_start (text)\n events.push({\n type: \"content_block_start\",\n index: blockIndex,\n content_block: { type: \"text\", text: \"\" },\n });\n\n // content_block_delta — text chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n type: \"content_block_delta\",\n index: blockIndex,\n delta: { type: \"text_delta\", text: slice },\n });\n }\n\n // content_block_stop\n events.push({\n type: \"content_block_stop\",\n index: blockIndex,\n });\n\n // message_delta\n events.push({\n type: \"message_delta\",\n delta: {\n stop_reason: claudeStopReason(overrides?.finishReason, \"end_turn\"),\n stop_sequence: null,\n },\n usage: { output_tokens: claudeUsage(overrides).output_tokens },\n });\n\n // message_stop\n events.push({ type: \"message_stop\" });\n\n return events;\n}\n\nfunction buildClaudeToolCallStreamEvents(\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n logger: Logger,\n overrides?: ResponseOverrides,\n): ClaudeSSEEvent[] {\n const msgId = overrides?.id ?? generateMessageId();\n const effectiveModel = overrides?.model ?? model;\n const events: ClaudeSSEEvent[] = [];\n\n // message_start\n events.push({\n type: \"message_start\",\n message: {\n id: msgId,\n type: \"message\",\n role: overrides?.role ?? \"assistant\",\n content: [],\n model: effectiveModel,\n stop_reason: null,\n stop_sequence: null,\n usage: claudeUsage(overrides),\n },\n });\n\n for (let idx = 0; idx < toolCalls.length; idx++) {\n const tc = toolCalls[idx];\n const toolUseId = tc.id || generateToolUseId();\n\n // Parse arguments to JSON object (Claude uses objects, not strings)\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 const argsJson = JSON.stringify(argsObj);\n\n // content_block_start\n events.push({\n type: \"content_block_start\",\n index: idx,\n content_block: {\n type: \"tool_use\",\n id: toolUseId,\n name: tc.name,\n input: {},\n },\n });\n\n // content_block_delta — input_json_delta chunks\n for (let i = 0; i < argsJson.length; i += chunkSize) {\n const slice = argsJson.slice(i, i + chunkSize);\n events.push({\n type: \"content_block_delta\",\n index: idx,\n delta: { type: \"input_json_delta\", partial_json: slice },\n });\n }\n\n // content_block_stop\n events.push({\n type: \"content_block_stop\",\n index: idx,\n });\n }\n\n // message_delta\n events.push({\n type: \"message_delta\",\n delta: {\n stop_reason: claudeStopReason(overrides?.finishReason, \"tool_use\"),\n stop_sequence: null,\n },\n usage: { output_tokens: claudeUsage(overrides).output_tokens },\n });\n\n // message_stop\n events.push({ type: \"message_stop\" });\n\n return events;\n}\n\n// Non-streaming response builders\n\nfunction buildClaudeTextResponse(\n content: string,\n model: string,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): object {\n const contentBlocks: object[] = [];\n\n if (reasoning) {\n contentBlocks.push({ type: \"thinking\", thinking: reasoning, signature: \"\" });\n }\n\n contentBlocks.push({ type: \"text\", text: content });\n\n return {\n id: overrides?.id ?? generateMessageId(),\n type: \"message\",\n role: overrides?.role ?? \"assistant\",\n content: contentBlocks,\n model: overrides?.model ?? model,\n stop_reason: claudeStopReason(overrides?.finishReason, \"end_turn\"),\n stop_sequence: null,\n usage: claudeUsage(overrides),\n };\n}\n\nfunction buildClaudeToolCallResponse(\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): object {\n return {\n id: overrides?.id ?? generateMessageId(),\n type: \"message\",\n role: overrides?.role ?? \"assistant\",\n content: 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 type: \"tool_use\",\n id: tc.id || generateToolUseId(),\n name: tc.name,\n input: argsObj,\n };\n }),\n model: overrides?.model ?? model,\n stop_reason: claudeStopReason(overrides?.finishReason, \"tool_use\"),\n stop_sequence: null,\n usage: claudeUsage(overrides),\n };\n}\n\nfunction buildClaudeContentWithToolCallsStreamEvents(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): ClaudeSSEEvent[] {\n const msgId = overrides?.id ?? generateMessageId();\n const effectiveModel = overrides?.model ?? model;\n const events: ClaudeSSEEvent[] = [];\n\n // message_start\n events.push({\n type: \"message_start\",\n message: {\n id: msgId,\n type: \"message\",\n role: overrides?.role ?? \"assistant\",\n content: [],\n model: effectiveModel,\n stop_reason: null,\n stop_sequence: null,\n usage: claudeUsage(overrides),\n },\n });\n\n let blockIndex = 0;\n\n // Optional thinking block\n if (reasoning) {\n events.push({\n type: \"content_block_start\",\n index: blockIndex,\n content_block: { type: \"thinking\", thinking: \"\", signature: \"\" },\n });\n\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n events.push({\n type: \"content_block_delta\",\n index: blockIndex,\n delta: { type: \"thinking_delta\", thinking: slice },\n });\n }\n\n events.push({\n type: \"content_block_delta\",\n index: blockIndex,\n delta: { type: \"signature_delta\", signature: \"\" },\n });\n\n events.push({\n type: \"content_block_stop\",\n index: blockIndex,\n });\n\n blockIndex++;\n }\n\n // Text content block\n events.push({\n type: \"content_block_start\",\n index: blockIndex,\n content_block: { type: \"text\", text: \"\" },\n });\n\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n type: \"content_block_delta\",\n index: blockIndex,\n delta: { type: \"text_delta\", text: slice },\n });\n }\n\n events.push({\n type: \"content_block_stop\",\n index: blockIndex,\n });\n\n blockIndex++;\n\n // Tool use blocks\n for (const tc of toolCalls) {\n const toolUseId = tc.id || generateToolUseId();\n\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 const argsJson = JSON.stringify(argsObj);\n\n events.push({\n type: \"content_block_start\",\n index: blockIndex,\n content_block: {\n type: \"tool_use\",\n id: toolUseId,\n name: tc.name,\n input: {},\n },\n });\n\n for (let i = 0; i < argsJson.length; i += chunkSize) {\n const slice = argsJson.slice(i, i + chunkSize);\n events.push({\n type: \"content_block_delta\",\n index: blockIndex,\n delta: { type: \"input_json_delta\", partial_json: slice },\n });\n }\n\n events.push({\n type: \"content_block_stop\",\n index: blockIndex,\n });\n\n blockIndex++;\n }\n\n // message_delta\n events.push({\n type: \"message_delta\",\n delta: {\n stop_reason: claudeStopReason(overrides?.finishReason, \"tool_use\"),\n stop_sequence: null,\n },\n usage: { output_tokens: claudeUsage(overrides).output_tokens },\n });\n\n // message_stop\n events.push({ type: \"message_stop\" });\n\n return events;\n}\n\nfunction buildClaudeContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): object {\n const contentBlocks: object[] = [];\n\n if (reasoning) {\n contentBlocks.push({ type: \"thinking\", thinking: reasoning, signature: \"\" });\n }\n\n contentBlocks.push({ type: \"text\", text: content });\n\n for (const tc of toolCalls) {\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 contentBlocks.push({\n type: \"tool_use\",\n id: tc.id || generateToolUseId(),\n name: tc.name,\n input: argsObj,\n });\n }\n\n return {\n id: overrides?.id ?? generateMessageId(),\n type: \"message\",\n role: overrides?.role ?? \"assistant\",\n content: contentBlocks,\n model: overrides?.model ?? model,\n stop_reason: claudeStopReason(overrides?.finishReason, \"tool_use\"),\n stop_sequence: null,\n usage: claudeUsage(overrides),\n };\n}\n\n// ─── SSE writer for Claude Messages API ─────────────────────────────────────\n\ninterface ClaudeStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nasync function writeClaudeSSEStream(\n res: http.ServerResponse,\n events: ClaudeSSEEvent[],\n optionsOrLatency?: number | ClaudeStreamOptions,\n): Promise<boolean> {\n const opts: ClaudeStreamOptions =\n typeof optionsOrLatency === \"number\" ? { latency: optionsOrLatency } : (optionsOrLatency ?? {});\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\", \"text/event-stream\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"keep-alive\");\n\n let chunkIndex = 0;\n for (const event of events) {\n const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);\n if (chunkDelay > 0) await delay(chunkDelay, signal);\n if (signal?.aborted) return false;\n if (res.writableEnded) return true;\n res.write(`event: ${event.type}\\ndata: ${JSON.stringify(event)}\\n\\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\n// ─── Request handler ────────────────────────────────────────────────────────\n\nexport async function handleMessages(\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 let claudeReq: ClaudeRequest;\n try {\n claudeReq = JSON.parse(raw) as ClaudeRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/messages\",\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: ${detail}`,\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = claudeToCompletionRequest(claudeReq);\n\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n const lastUserMsg = completionReq.messages.filter((m) => m.role === \"user\").pop();\n const snippet =\n typeof lastUserMsg?.content === \"string\" ? lastUserMsg.content.slice(0, 80) : \"\";\n logger.debug(\n `No fixture matched for request (model=${completionReq.model ?? \"?\"}, msg=\"${snippet}\")`,\n );\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n {\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/messages\",\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 = \"Strict mode: no fixture matched\";\n logger.error(\n `STRICT: No fixture matched for ${req.method ?? \"POST\"} ${req.url ?? \"/v1/messages\"}`,\n );\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/messages\",\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 \"anthropic\",\n req.url ?? \"/v1/messages\",\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: req.url ?? \"/v1/messages\",\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: req.url ?? \"/v1/messages\",\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 // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/messages\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n // Anthropic-style error format: { type: \"error\", error: { type, message } }\n const anthropicError = {\n type: \"error\",\n error: {\n type: response.error.type ?? \"api_error\",\n message: response.error.message,\n },\n };\n writeErrorResponse(res, status, JSON.stringify(anthropicError), {\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(\n \"webSearches in fixture response are not supported for Claude Messages API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/messages\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (claudeReq.stream !== true) {\n const body = buildClaudeContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n completionReq.model,\n logger,\n response.reasoning,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildClaudeContentWithToolCallsStreamEvents(\n response.content,\n response.toolCalls,\n completionReq.model,\n chunkSize,\n logger,\n response.reasoning,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeClaudeSSEStream(res, events, {\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(\n \"webSearches in fixture response are not supported for Claude Messages API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/messages\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (claudeReq.stream !== true) {\n const body = buildClaudeTextResponse(\n response.content,\n completionReq.model,\n response.reasoning,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildClaudeTextStreamEvents(\n response.content,\n completionReq.model,\n chunkSize,\n response.reasoning,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeClaudeSSEStream(res, events, {\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(\n \"webSearches in fixture response are not supported for Claude Messages API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v1/messages\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (claudeReq.stream !== true) {\n const body = buildClaudeToolCallResponse(\n response.toolCalls,\n completionReq.model,\n logger,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildClaudeToolCallStreamEvents(\n response.toolCalls,\n completionReq.model,\n chunkSize,\n logger,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeClaudeSSEStream(res, events, {\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: req.url ?? \"/v1/messages\",\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: {\n message: \"Fixture response did not match any known type\",\n type: \"server_error\",\n },\n }),\n );\n}\n"],"mappings":";;;;;;;;AAgFA,SAAS,yBAAyB,SAAgD;AAChF,KAAI,OAAO,YAAY,SAAU,QAAO;AACxC,QAAO,QACJ,QAAQ,MAAM,EAAE,SAAS,OAAO,CAChC,KAAK,MAAM,EAAE,QAAQ,GAAG,CACxB,KAAK,GAAG;;AAGb,SAAgB,0BAA0B,KAA2C;CACnF,MAAM,WAA0B,EAAE;AAGlC,KAAI,IAAI,QAAQ;EACd,MAAM,aACJ,OAAO,IAAI,WAAW,WAClB,IAAI,SACJ,IAAI,OACD,QAAQ,MAAM,EAAE,SAAS,OAAO,CAChC,KAAK,MAAM,EAAE,QAAQ,GAAG,CACxB,KAAK,GAAG;AACjB,MAAI,WACF,UAAS,KAAK;GAAE,MAAM;GAAU,SAAS;GAAY,CAAC;;AAI1D,MAAK,MAAM,OAAO,IAAI,SACpB,KAAI,IAAI,SAAS,QAAQ;AAEvB,MAAI,OAAO,IAAI,YAAY,YAAY,MAAM,QAAQ,IAAI,QAAQ,EAAE;GACjE,MAAM,cAAc,IAAI,QAAQ,QAAQ,MAAM,EAAE,SAAS,cAAc;GACvE,MAAM,aAAa,IAAI,QAAQ,QAAQ,MAAM,EAAE,SAAS,OAAO;AAE/D,OAAI,YAAY,SAAS,GAAG;AAE1B,SAAK,MAAM,MAAM,aAAa;KAC5B,MAAM,gBACJ,OAAO,GAAG,YAAY,WAClB,GAAG,UACH,MAAM,QAAQ,GAAG,QAAQ,GACvB,GAAG,QACA,QAAQ,MAAM,EAAE,SAAS,OAAO,CAChC,KAAK,MAAM,EAAE,QAAQ,GAAG,CACxB,KAAK,GAAG,GACX;AACR,cAAS,KAAK;MACZ,MAAM;MACN,SAAS;MACT,cAAc,GAAG;MAClB,CAAC;;AAGJ,QAAI,WAAW,SAAS,EACtB,UAAS,KAAK;KACZ,MAAM;KACN,SAAS,WAAW,KAAK,MAAM,EAAE,QAAQ,GAAG,CAAC,KAAK,GAAG;KACtD,CAAC;AAEJ;;;AAIJ,WAAS,KAAK;GACZ,MAAM;GACN,SAAS,yBAAyB,IAAI,QAAQ;GAC/C,CAAC;YACO,IAAI,SAAS,YACtB,KAAI,OAAO,IAAI,YAAY,SACzB,UAAS,KAAK;EAAE,MAAM;EAAa,SAAS,IAAI;EAAS,CAAC;UACjD,MAAM,QAAQ,IAAI,QAAQ,EAAE;EACrC,MAAM,gBAAgB,IAAI,QAAQ,QAAQ,MAAM,EAAE,SAAS,WAAW;EACtE,MAAM,cAAc,yBAAyB,IAAI,QAAQ;AAEzD,MAAI,cAAc,SAAS,EACzB,UAAS,KAAK;GACZ,MAAM;GACN,SAAS,eAAe;GACxB,YAAY,cAAc,KAAK,OAAO;IACpC,IAAI,EAAE,MAAM,mBAAmB;IAC/B,MAAM;IACN,UAAU;KACR,MAAM,EAAE,QAAQ;KAChB,WAAW,OAAO,EAAE,UAAU,WAAW,EAAE,QAAQ,KAAK,UAAU,EAAE,SAAS,EAAE,CAAC;KACjF;IACF,EAAE;GACJ,CAAC;MAEF,UAAS,KAAK;GAAE,MAAM;GAAa,SAAS,eAAe;GAAM,CAAC;OAIpE,UAAS,KAAK;EAAE,MAAM;EAAa,SAAS;EAAM,CAAC;CAMzD,IAAI;AACJ,KAAI,IAAI,SAAS,IAAI,MAAM,SAAS,EAClC,SAAQ,IAAI,MAAM,KAAK,OAAO;EAC5B,MAAM;EACN,UAAU;GACR,MAAM,EAAE;GACR,aAAa,EAAE;GACf,YAAY,EAAE;GACf;EACF,EAAE;AAGL,QAAO;EACL,OAAO,IAAI;EACX;EACA,QAAQ,IAAI;EACZ,aAAa,IAAI;EACjB,YAAY,IAAI;EAChB;EACA,eAAe;EAChB;;AAKH,SAAS,iBAAiB,cAAkC,eAA+B;AACzF,KAAI,CAAC,aAAc,QAAO;AAC1B,KAAI,iBAAiB,OAAQ,QAAO;AACpC,KAAI,iBAAiB,aAAc,QAAO;AAC1C,KAAI,iBAAiB,SAAU,QAAO;AACtC,QAAO;;AAGT,SAAS,YAAY,WAGnB;AACA,KAAI,CAAC,WAAW,MAAO,QAAO;EAAE,cAAc;EAAG,eAAe;EAAG;AACnE,QAAO;EACL,cAAc,UAAU,MAAM,gBAAgB,UAAU,MAAM,iBAAiB;EAC/E,eAAe,UAAU,MAAM,iBAAiB,UAAU,MAAM,qBAAqB;EACtF;;AAQH,SAAS,4BACP,SACA,OACA,WACA,WACA,WACkB;CAClB,MAAM,QAAQ,WAAW,MAAM,mBAAmB;CAClD,MAAM,iBAAiB,WAAW,SAAS;CAC3C,MAAM,SAA2B,EAAE;AAGnC,QAAO,KAAK;EACV,MAAM;EACN,SAAS;GACP,IAAI;GACJ,MAAM;GACN,MAAM,WAAW,QAAQ;GACzB,SAAS,EAAE;GACX,OAAO;GACP,aAAa;GACb,eAAe;GACf,OAAO,YAAY,UAAU;GAC9B;EACF,CAAC;CAEF,IAAI,aAAa;AAGjB,KAAI,WAAW;AACb,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACP,eAAe;IAAE,MAAM;IAAY,UAAU;IAAI,WAAW;IAAI;GACjE,CAAC;AAEF,OAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK,WAAW;GACpD,MAAM,QAAQ,UAAU,MAAM,GAAG,IAAI,UAAU;AAC/C,UAAO,KAAK;IACV,MAAM;IACN,OAAO;IACP,OAAO;KAAE,MAAM;KAAkB,UAAU;KAAO;IACnD,CAAC;;AAGJ,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACP,OAAO;IAAE,MAAM;IAAmB,WAAW;IAAI;GAClD,CAAC;AAEF,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACR,CAAC;AAEF;;AAIF,QAAO,KAAK;EACV,MAAM;EACN,OAAO;EACP,eAAe;GAAE,MAAM;GAAQ,MAAM;GAAI;EAC1C,CAAC;AAGF,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,WAAW;EAClD,MAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,UAAU;AAC7C,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACP,OAAO;IAAE,MAAM;IAAc,MAAM;IAAO;GAC3C,CAAC;;AAIJ,QAAO,KAAK;EACV,MAAM;EACN,OAAO;EACR,CAAC;AAGF,QAAO,KAAK;EACV,MAAM;EACN,OAAO;GACL,aAAa,iBAAiB,WAAW,cAAc,WAAW;GAClE,eAAe;GAChB;EACD,OAAO,EAAE,eAAe,YAAY,UAAU,CAAC,eAAe;EAC/D,CAAC;AAGF,QAAO,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAErC,QAAO;;AAGT,SAAS,gCACP,WACA,OACA,WACA,QACA,WACkB;CAClB,MAAM,QAAQ,WAAW,MAAM,mBAAmB;CAClD,MAAM,iBAAiB,WAAW,SAAS;CAC3C,MAAM,SAA2B,EAAE;AAGnC,QAAO,KAAK;EACV,MAAM;EACN,SAAS;GACP,IAAI;GACJ,MAAM;GACN,MAAM,WAAW,QAAQ;GACzB,SAAS,EAAE;GACX,OAAO;GACP,aAAa;GACb,eAAe;GACf,OAAO,YAAY,UAAU;GAC9B;EACF,CAAC;AAEF,MAAK,IAAI,MAAM,GAAG,MAAM,UAAU,QAAQ,OAAO;EAC/C,MAAM,KAAK,UAAU;EACrB,MAAM,YAAY,GAAG,MAAM,mBAAmB;EAG9C,IAAI;AACJ,MAAI;AACF,aAAU,KAAK,MAAM,GAAG,aAAa,KAAK;UACpC;AACN,UAAO,KACL,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,aAAU,EAAE;;EAEd,MAAM,WAAW,KAAK,UAAU,QAAQ;AAGxC,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACP,eAAe;IACb,MAAM;IACN,IAAI;IACJ,MAAM,GAAG;IACT,OAAO,EAAE;IACV;GACF,CAAC;AAGF,OAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK,WAAW;GACnD,MAAM,QAAQ,SAAS,MAAM,GAAG,IAAI,UAAU;AAC9C,UAAO,KAAK;IACV,MAAM;IACN,OAAO;IACP,OAAO;KAAE,MAAM;KAAoB,cAAc;KAAO;IACzD,CAAC;;AAIJ,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACR,CAAC;;AAIJ,QAAO,KAAK;EACV,MAAM;EACN,OAAO;GACL,aAAa,iBAAiB,WAAW,cAAc,WAAW;GAClE,eAAe;GAChB;EACD,OAAO,EAAE,eAAe,YAAY,UAAU,CAAC,eAAe;EAC/D,CAAC;AAGF,QAAO,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAErC,QAAO;;AAKT,SAAS,wBACP,SACA,OACA,WACA,WACQ;CACR,MAAM,gBAA0B,EAAE;AAElC,KAAI,UACF,eAAc,KAAK;EAAE,MAAM;EAAY,UAAU;EAAW,WAAW;EAAI,CAAC;AAG9E,eAAc,KAAK;EAAE,MAAM;EAAQ,MAAM;EAAS,CAAC;AAEnD,QAAO;EACL,IAAI,WAAW,MAAM,mBAAmB;EACxC,MAAM;EACN,MAAM,WAAW,QAAQ;EACzB,SAAS;EACT,OAAO,WAAW,SAAS;EAC3B,aAAa,iBAAiB,WAAW,cAAc,WAAW;EAClE,eAAe;EACf,OAAO,YAAY,UAAU;EAC9B;;AAGH,SAAS,4BACP,WACA,OACA,QACA,WACQ;AACR,QAAO;EACL,IAAI,WAAW,MAAM,mBAAmB;EACxC,MAAM;EACN,MAAM,WAAW,QAAQ;EACzB,SAAS,UAAU,KAAK,OAAO;GAC7B,IAAI;AACJ,OAAI;AACF,cAAU,KAAK,MAAM,GAAG,aAAa,KAAK;WACpC;AACN,WAAO,KACL,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,cAAU,EAAE;;AAEd,UAAO;IACL,MAAM;IACN,IAAI,GAAG,MAAM,mBAAmB;IAChC,MAAM,GAAG;IACT,OAAO;IACR;IACD;EACF,OAAO,WAAW,SAAS;EAC3B,aAAa,iBAAiB,WAAW,cAAc,WAAW;EAClE,eAAe;EACf,OAAO,YAAY,UAAU;EAC9B;;AAGH,SAAS,4CACP,SACA,WACA,OACA,WACA,QACA,WACA,WACkB;CAClB,MAAM,QAAQ,WAAW,MAAM,mBAAmB;CAClD,MAAM,iBAAiB,WAAW,SAAS;CAC3C,MAAM,SAA2B,EAAE;AAGnC,QAAO,KAAK;EACV,MAAM;EACN,SAAS;GACP,IAAI;GACJ,MAAM;GACN,MAAM,WAAW,QAAQ;GACzB,SAAS,EAAE;GACX,OAAO;GACP,aAAa;GACb,eAAe;GACf,OAAO,YAAY,UAAU;GAC9B;EACF,CAAC;CAEF,IAAI,aAAa;AAGjB,KAAI,WAAW;AACb,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACP,eAAe;IAAE,MAAM;IAAY,UAAU;IAAI,WAAW;IAAI;GACjE,CAAC;AAEF,OAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK,WAAW;GACpD,MAAM,QAAQ,UAAU,MAAM,GAAG,IAAI,UAAU;AAC/C,UAAO,KAAK;IACV,MAAM;IACN,OAAO;IACP,OAAO;KAAE,MAAM;KAAkB,UAAU;KAAO;IACnD,CAAC;;AAGJ,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACP,OAAO;IAAE,MAAM;IAAmB,WAAW;IAAI;GAClD,CAAC;AAEF,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACR,CAAC;AAEF;;AAIF,QAAO,KAAK;EACV,MAAM;EACN,OAAO;EACP,eAAe;GAAE,MAAM;GAAQ,MAAM;GAAI;EAC1C,CAAC;AAEF,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,WAAW;EAClD,MAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,UAAU;AAC7C,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACP,OAAO;IAAE,MAAM;IAAc,MAAM;IAAO;GAC3C,CAAC;;AAGJ,QAAO,KAAK;EACV,MAAM;EACN,OAAO;EACR,CAAC;AAEF;AAGA,MAAK,MAAM,MAAM,WAAW;EAC1B,MAAM,YAAY,GAAG,MAAM,mBAAmB;EAE9C,IAAI;AACJ,MAAI;AACF,aAAU,KAAK,MAAM,GAAG,aAAa,KAAK;UACpC;AACN,UAAO,KACL,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,aAAU,EAAE;;EAEd,MAAM,WAAW,KAAK,UAAU,QAAQ;AAExC,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACP,eAAe;IACb,MAAM;IACN,IAAI;IACJ,MAAM,GAAG;IACT,OAAO,EAAE;IACV;GACF,CAAC;AAEF,OAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK,WAAW;GACnD,MAAM,QAAQ,SAAS,MAAM,GAAG,IAAI,UAAU;AAC9C,UAAO,KAAK;IACV,MAAM;IACN,OAAO;IACP,OAAO;KAAE,MAAM;KAAoB,cAAc;KAAO;IACzD,CAAC;;AAGJ,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACR,CAAC;AAEF;;AAIF,QAAO,KAAK;EACV,MAAM;EACN,OAAO;GACL,aAAa,iBAAiB,WAAW,cAAc,WAAW;GAClE,eAAe;GAChB;EACD,OAAO,EAAE,eAAe,YAAY,UAAU,CAAC,eAAe;EAC/D,CAAC;AAGF,QAAO,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAErC,QAAO;;AAGT,SAAS,wCACP,SACA,WACA,OACA,QACA,WACA,WACQ;CACR,MAAM,gBAA0B,EAAE;AAElC,KAAI,UACF,eAAc,KAAK;EAAE,MAAM;EAAY,UAAU;EAAW,WAAW;EAAI,CAAC;AAG9E,eAAc,KAAK;EAAE,MAAM;EAAQ,MAAM;EAAS,CAAC;AAEnD,MAAK,MAAM,MAAM,WAAW;EAC1B,IAAI;AACJ,MAAI;AACF,aAAU,KAAK,MAAM,GAAG,aAAa,KAAK;UACpC;AACN,UAAO,KACL,sDAAsD,GAAG,KAAK,KAAK,GAAG,YACvE;AACD,aAAU,EAAE;;AAEd,gBAAc,KAAK;GACjB,MAAM;GACN,IAAI,GAAG,MAAM,mBAAmB;GAChC,MAAM,GAAG;GACT,OAAO;GACR,CAAC;;AAGJ,QAAO;EACL,IAAI,WAAW,MAAM,mBAAmB;EACxC,MAAM;EACN,MAAM,WAAW,QAAQ;EACzB,SAAS;EACT,OAAO,WAAW,SAAS;EAC3B,aAAa,iBAAiB,WAAW,cAAc,WAAW;EAClE,eAAe;EACf,OAAO,YAAY,UAAU;EAC9B;;AAcH,eAAe,qBACb,KACA,QACA,kBACkB;CAClB,MAAM,OACJ,OAAO,qBAAqB,WAAW,EAAE,SAAS,kBAAkB,GAAI,oBAAoB,EAAE;CAChG,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,oBAAoB;AAClD,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,EAAG,OAAM,MAAM,YAAY,OAAO;AACnD,MAAI,QAAQ,QAAS,QAAO;AAC5B,MAAI,IAAI,cAAe,QAAO;AAC9B,MAAI,MAAM,UAAU,MAAM,KAAK,UAAU,KAAK,UAAU,MAAM,CAAC,MAAM;AACrE,iBAAe;AACf,MAAI,QAAQ,QAAS,QAAO;AAC5B;;AAGF,KAAI,CAAC,IAAI,cACP,KAAI,KAAK;AAEX,QAAO;;AAKT,eAAsB,eACpB,KACA,KACA,KACA,UACA,SACA,UACA,gBACe;CACf,MAAM,EAAE,WAAW;AACnB,gBAAe,IAAI;CAEnB,IAAI;AACJ,KAAI;AACF,cAAY,KAAK,MAAM,IAAI;UACpB,UAAU;EACjB,MAAM,SAAS,oBAAoB,QAAQ,SAAS,UAAU;AAC9D,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS,mBAAmB;GAC5B,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAIF,MAAM,gBAAgB,0BAA0B,UAAU;CAE1D,MAAM,SAAS,UAAU,IAAI;CAC7B,MAAM,UAAU,aACd,UACA,eACA,QAAQ,6BAA6B,OAAO,EAC5C,SAAS,iBACV;AAED,KAAI,SAAS;AACX,UAAQ,2BAA2B,SAAS,UAAU,OAAO;AAC7D,SAAO,MAAM,oBAAoB,KAAK,UAAU,QAAQ,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG;QAC1E;EACL,MAAM,cAAc,cAAc,SAAS,QAAQ,MAAM,EAAE,SAAS,OAAO,CAAC,KAAK;EACjF,MAAM,UACJ,OAAO,aAAa,YAAY,WAAW,YAAY,QAAQ,MAAM,GAAG,GAAG,GAAG;AAChF,SAAO,MACL,yCAAyC,cAAc,SAAS,IAAI,SAAS,QAAQ,IACtF;;AAGH,KACE,WACE,KACA,SACA,SAAS,OACT,IAAI,SACJ,SACA;EACE,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAAS,eAAe,IAAI,QAAQ;EACpC,MAAM;EACP,EACD,UAAU,YAAY,SACtB,SAAS,UACT,SAAS,OACV,CAED;AAEF,KAAI,CAAC,SAAS;AAEZ,MADwB,kBAAkB,SAAS,QAAQ,IAAI,QAAQ,EAClD;GACnB,MAAM,eAAe;GACrB,MAAM,gBAAgB;AACtB,UAAO,MACL,kCAAkC,IAAI,UAAU,OAAO,GAAG,IAAI,OAAO,iBACtE;AACD,WAAQ,IAAI;IACV,QAAQ,IAAI,UAAU;IACtB,MAAM,IAAI,OAAO;IACjB,SAAS,eAAe,IAAI,QAAQ;IACpC,MAAM;IACN,UAAU;KACR,QAAQ;KACR,SAAS;KACT,GAAG,oBAAoB,SAAS,QAAQ,IAAI,QAAQ;KACrD;IACF,CAAC;AACF,sBACE,KACA,cACA,KAAK,UAAU,EACb,OAAO;IACL,SAAS;IACT,MAAM;IACP,EACF,CAAC,CACH;AACD;;AAEF,MAAI,SAAS,QAAQ;GACnB,MAAM,UAAU,MAAM,eACpB,KACA,KACA,eACA,aACA,IAAI,OAAO,gBACX,UACA,UACA,IACD;AACD,OAAI,YAAY,kBAAmB;AACnC,OAAI,YAAY,kBAAkB;AAChC,YAAQ,IAAI;KACV,QAAQ,IAAI,UAAU;KACtB,MAAM,IAAI,OAAO;KACjB,SAAS,eAAe,IAAI,QAAQ;KACpC,MAAM;KACN,UAAU;MAAE,QAAQ,IAAI,cAAc;MAAK,SAAS;MAAM,QAAQ;MAAS;KAC5E,CAAC;AACF;;;AAGJ,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IACR,QAAQ;IACR,SAAS;IACT,GAAG,oBAAoB,SAAS,QAAQ,IAAI,QAAQ;IACrD;GACF,CAAC;AACF,qBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAGF,MAAM,WAAW,MAAM,gBAAgB,SAAS,cAAc;CAC9D,MAAM,UAAU,QAAQ,WAAW,SAAS;CAC5C,MAAM,YAAY,KAAK,IAAI,GAAG,QAAQ,aAAa,SAAS,UAAU;AAGtE,KAAI,gBAAgB,SAAS,EAAE;EAC7B,MAAM,SAAS,SAAS,UAAU;AAClC,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE;IAAQ;IAAS;GAC9B,CAAC;EAEF,MAAM,iBAAiB;GACrB,MAAM;GACN,OAAO;IACL,MAAM,SAAS,MAAM,QAAQ;IAC7B,SAAS,SAAS,MAAM;IACzB;GACF;AACD,qBAAmB,KAAK,QAAQ,KAAK,UAAU,eAAe,EAAE,EAC9D,YAAY,SAAS,YACtB,CAAC;AACF;;AAIF,KAAI,+BAA+B,SAAS,EAAE;AAC5C,MAAI,SAAS,aAAa,OACxB,QAAO,KACL,uFACD;EAEH,MAAM,YAAY,iBAAiB,SAAS;EAC5C,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,MAAI,UAAU,WAAW,MAAM;GAC7B,MAAM,OAAO,wCACX,SAAS,SACT,SAAS,WACT,cAAc,OACd,QACA,SAAS,WACT,UACD;AACD,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,KAAK,CAAC;SACxB;GACL,MAAM,SAAS,4CACb,SAAS,SACT,SAAS,WACT,cAAc,OACd,WACA,QACA,SAAS,WACT,UACD;GACD,MAAM,eAAe,yBAAyB,QAAQ;AAStD,OAAI,CARc,MAAM,qBAAqB,KAAK,QAAQ;IACxD;IACA,kBAAkB,QAAQ;IAC1B,iBAAiB,QAAQ;IACzB,aAAa,QAAQ,eAAe,SAAS;IAC7C,QAAQ,cAAc;IACtB,aAAa,cAAc;IAC5B,CAAC,EACc;AACd,QAAI,CAAC,IAAI,cAAe,KAAI,SAAS;AACrC,iBAAa,SAAS,cAAc;AACpC,iBAAa,SAAS,kBAAkB,cAAc,QAAQ;;AAEhE,iBAAc,SAAS;;AAEzB;;AAIF,KAAI,eAAe,SAAS,EAAE;AAC5B,MAAI,SAAS,aAAa,OACxB,QAAO,KACL,uFACD;EAEH,MAAM,YAAY,iBAAiB,SAAS;EAC5C,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,MAAI,UAAU,WAAW,MAAM;GAC7B,MAAM,OAAO,wBACX,SAAS,SACT,cAAc,OACd,SAAS,WACT,UACD;AACD,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,KAAK,CAAC;SACxB;GACL,MAAM,SAAS,4BACb,SAAS,SACT,cAAc,OACd,WACA,SAAS,WACT,UACD;GACD,MAAM,eAAe,yBAAyB,QAAQ;AAStD,OAAI,CARc,MAAM,qBAAqB,KAAK,QAAQ;IACxD;IACA,kBAAkB,QAAQ;IAC1B,iBAAiB,QAAQ;IACzB,aAAa,QAAQ,eAAe,SAAS;IAC7C,QAAQ,cAAc;IACtB,aAAa,cAAc;IAC5B,CAAC,EACc;AACd,QAAI,CAAC,IAAI,cAAe,KAAI,SAAS;AACrC,iBAAa,SAAS,cAAc;AACpC,iBAAa,SAAS,kBAAkB,cAAc,QAAQ;;AAEhE,iBAAc,SAAS;;AAEzB;;AAIF,KAAI,mBAAmB,SAAS,EAAE;AAChC,MAAI,SAAS,aAAa,OACxB,QAAO,KACL,uFACD;EAEH,MAAM,YAAY,iBAAiB,SAAS;EAC5C,MAAM,eAAe,QAAQ,IAAI;GAC/B,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,MAAI,UAAU,WAAW,MAAM;GAC7B,MAAM,OAAO,4BACX,SAAS,WACT,cAAc,OACd,QACA,UACD;AACD,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,KAAK,CAAC;SACxB;GACL,MAAM,SAAS,gCACb,SAAS,WACT,cAAc,OACd,WACA,QACA,UACD;GACD,MAAM,eAAe,yBAAyB,QAAQ;AAStD,OAAI,CARc,MAAM,qBAAqB,KAAK,QAAQ;IACxD;IACA,kBAAkB,QAAQ;IAC1B,iBAAiB,QAAQ;IACzB,aAAa,QAAQ,eAAe,SAAS;IAC7C,QAAQ,cAAc;IACtB,aAAa,cAAc;IAC5B,CAAC,EACc;AACd,QAAI,CAAC,IAAI,cAAe,KAAI,SAAS;AACrC,iBAAa,SAAS,cAAc;AACpC,iBAAa,SAAS,kBAAkB,cAAc,QAAQ;;AAEhE,iBAAc,SAAS;;AAEzB;;AAIF,SAAQ,IAAI;EACV,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAAS,eAAe,IAAI,QAAQ;EACpC,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK;GAAS;EACnC,CAAC;AACF,oBACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;EACL,SAAS;EACT,MAAM;EACP,EACF,CAAC,CACH"}
package/dist/metrics.cjs CHANGED
@@ -137,6 +137,7 @@ function createMetricsRegistry() {
137
137
  const BEDROCK_RE = /^\/model\/([^/]+)\/(invoke|invoke-with-response-stream|converse|converse-stream)$/;
138
138
  const GEMINI_RE = /^\/v1beta\/models\/([^:]+):(generateContent|streamGenerateContent)$/;
139
139
  const AZURE_RE = /^\/openai\/deployments\/([^/]+)\/(chat\/completions|embeddings)$/;
140
+ const ELEVENLABS_TTS_RE = /^\/v1\/text-to-speech\/([^/]+)$/;
140
141
  const VERTEX_RE = /^\/v1\/projects\/([^/]+)\/locations\/([^/]+)\/publishers\/google\/models\/([^:]+):(.+)$/;
141
142
  /**
142
143
  * Normalize parametric API paths to route patterns for use as metric labels.
@@ -151,6 +152,7 @@ function normalizePathLabel(pathname) {
151
152
  if (azureMatch) return `/openai/deployments/{id}/${azureMatch[2]}`;
152
153
  const vertexMatch = pathname.match(VERTEX_RE);
153
154
  if (vertexMatch) return `/v1/projects/{p}/locations/{l}/publishers/google/models/{m}:${vertexMatch[4]}`;
155
+ if (ELEVENLABS_TTS_RE.test(pathname)) return "/v1/text-to-speech/{voice_id}";
154
156
  return pathname;
155
157
  }
156
158
 
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.cjs","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)$/;\nconst GEMINI_RE = /^\\/v1beta\\/models\\/([^:]+):(generateContent|streamGenerateContent)$/;\nconst AZURE_RE = /^\\/openai\\/deployments\\/([^/]+)\\/(chat\\/completions|embeddings)$/;\nconst VERTEX_RE =\n /^\\/v1\\/projects\\/([^/]+)\\/locations\\/([^/]+)\\/publishers\\/google\\/models\\/([^:]+):(.+)$/;\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 */\nexport function normalizePathLabel(pathname: string): string {\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}\n const geminiMatch = pathname.match(GEMINI_RE);\n if (geminiMatch) {\n return `/v1beta/models/{model}:${geminiMatch[2]}`;\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 return `/v1/projects/{p}/locations/{l}/publishers/google/models/{m}:${vertexMatch[4]}`;\n }\n\n // Static path — return as-is\n return pathname;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"metrics.cjs","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)$/;\nconst GEMINI_RE = /^\\/v1beta\\/models\\/([^:]+):(generateContent|streamGenerateContent)$/;\nconst AZURE_RE = /^\\/openai\\/deployments\\/([^/]+)\\/(chat\\/completions|embeddings)$/;\nconst ELEVENLABS_TTS_RE = /^\\/v1\\/text-to-speech\\/([^/]+)$/;\nconst VERTEX_RE =\n /^\\/v1\\/projects\\/([^/]+)\\/locations\\/([^/]+)\\/publishers\\/google\\/models\\/([^:]+):(.+)$/;\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 */\nexport function normalizePathLabel(pathname: string): string {\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}\n const geminiMatch = pathname.match(GEMINI_RE);\n if (geminiMatch) {\n return `/v1beta/models/{model}:${geminiMatch[2]}`;\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 return `/v1/projects/{p}/locations/{l}/publishers/google/models/{m}:${vertexMatch[4]}`;\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 // Static path — return as-is\n return pathname;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.d.cts","names":[],"sources":["../src/metrics.ts"],"sourcesContent":[],"mappings":";;AAWA;;;;;AAGuC,UAHtB,eAAA,CAGsB;EAoEvB,gBAAA,CAAA,IAAA,EAAA,MAAqB,EAAA,MAAI,EAtEA,MAsEA,CAAA,MAAe,EAAA,MAAA,CAAA,CAAA,EAAA,IAAA;EAkJxC,gBAAA,CAAA,IAAkB,EAAA,MAAA,EAAA,MAAA,EAvNO,MAuNP,CAAA,MAAA,EAAA,MAAA,CAAA,EAAA,KAAA,EAAA,MAAA,CAAA,EAAA,IAAA;iCAtND;;;;iBAoEjB,qBAAA,CAAA,GAAyB;;;;;iBAkJzB,kBAAA"}
1
+ {"version":3,"file":"metrics.d.cts","names":[],"sources":["../src/metrics.ts"],"sourcesContent":[],"mappings":";;AAWA;;;;;AAGuC,UAHtB,eAAA,CAGsB;EAoEvB,gBAAA,CAAA,IAAA,EAAA,MAAqB,EAAA,MAAI,EAtEA,MAsEA,CAAA,MAAe,EAAA,MAAA,CAAA,CAAA,EAAA,IAAA;EAmJxC,gBAAA,CAAA,IAAkB,EAAA,MAAA,EAAA,MAAA,EAxNO,MAwNP,CAAA,MAAA,EAAA,MAAA,CAAA,EAAA,KAAA,EAAA,MAAA,CAAA,EAAA,IAAA;iCAvND;;;;iBAoEjB,qBAAA,CAAA,GAAyB;;;;;iBAmJzB,kBAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.d.ts","names":[],"sources":["../src/metrics.ts"],"sourcesContent":[],"mappings":";;AAWA;;;;;AAGuC,UAHtB,eAAA,CAGsB;EAoEvB,gBAAA,CAAA,IAAA,EAAA,MAAqB,EAAA,MAAI,EAtEA,MAsEA,CAAA,MAAe,EAAA,MAAA,CAAA,CAAA,EAAA,IAAA;EAkJxC,gBAAA,CAAA,IAAkB,EAAA,MAAA,EAAA,MAAA,EAvNO,MAuNP,CAAA,MAAA,EAAA,MAAA,CAAA,EAAA,KAAA,EAAA,MAAA,CAAA,EAAA,IAAA;iCAtND;;;;iBAoEjB,qBAAA,CAAA,GAAyB;;;;;iBAkJzB,kBAAA"}
1
+ {"version":3,"file":"metrics.d.ts","names":[],"sources":["../src/metrics.ts"],"sourcesContent":[],"mappings":";;AAWA;;;;;AAGuC,UAHtB,eAAA,CAGsB;EAoEvB,gBAAA,CAAA,IAAA,EAAA,MAAqB,EAAA,MAAI,EAtEA,MAsEA,CAAA,MAAe,EAAA,MAAA,CAAA,CAAA,EAAA,IAAA;EAmJxC,gBAAA,CAAA,IAAkB,EAAA,MAAA,EAAA,MAAA,EAxNO,MAwNP,CAAA,MAAA,EAAA,MAAA,CAAA,EAAA,KAAA,EAAA,MAAA,CAAA,EAAA,IAAA;iCAvND;;;;iBAoEjB,qBAAA,CAAA,GAAyB;;;;;iBAmJzB,kBAAA"}
package/dist/metrics.js CHANGED
@@ -136,6 +136,7 @@ function createMetricsRegistry() {
136
136
  const BEDROCK_RE = /^\/model\/([^/]+)\/(invoke|invoke-with-response-stream|converse|converse-stream)$/;
137
137
  const GEMINI_RE = /^\/v1beta\/models\/([^:]+):(generateContent|streamGenerateContent)$/;
138
138
  const AZURE_RE = /^\/openai\/deployments\/([^/]+)\/(chat\/completions|embeddings)$/;
139
+ const ELEVENLABS_TTS_RE = /^\/v1\/text-to-speech\/([^/]+)$/;
139
140
  const VERTEX_RE = /^\/v1\/projects\/([^/]+)\/locations\/([^/]+)\/publishers\/google\/models\/([^:]+):(.+)$/;
140
141
  /**
141
142
  * Normalize parametric API paths to route patterns for use as metric labels.
@@ -150,6 +151,7 @@ function normalizePathLabel(pathname) {
150
151
  if (azureMatch) return `/openai/deployments/{id}/${azureMatch[2]}`;
151
152
  const vertexMatch = pathname.match(VERTEX_RE);
152
153
  if (vertexMatch) return `/v1/projects/{p}/locations/{l}/publishers/google/models/{m}:${vertexMatch[4]}`;
154
+ if (ELEVENLABS_TTS_RE.test(pathname)) return "/v1/text-to-speech/{voice_id}";
153
155
  return pathname;
154
156
  }
155
157
 
@@ -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)$/;\nconst GEMINI_RE = /^\\/v1beta\\/models\\/([^:]+):(generateContent|streamGenerateContent)$/;\nconst AZURE_RE = /^\\/openai\\/deployments\\/([^/]+)\\/(chat\\/completions|embeddings)$/;\nconst VERTEX_RE =\n /^\\/v1\\/projects\\/([^/]+)\\/locations\\/([^/]+)\\/publishers\\/google\\/models\\/([^:]+):(.+)$/;\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 */\nexport function normalizePathLabel(pathname: string): string {\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}\n const geminiMatch = pathname.match(GEMINI_RE);\n if (geminiMatch) {\n return `/v1beta/models/{model}:${geminiMatch[2]}`;\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 return `/v1/projects/{p}/locations/{l}/publishers/google/models/{m}:${vertexMatch[4]}`;\n }\n\n // Static path — return as-is\n return pathname;\n}\n"],"mappings":"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"}
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)$/;\nconst GEMINI_RE = /^\\/v1beta\\/models\\/([^:]+):(generateContent|streamGenerateContent)$/;\nconst AZURE_RE = /^\\/openai\\/deployments\\/([^/]+)\\/(chat\\/completions|embeddings)$/;\nconst ELEVENLABS_TTS_RE = /^\\/v1\\/text-to-speech\\/([^/]+)$/;\nconst VERTEX_RE =\n /^\\/v1\\/projects\\/([^/]+)\\/locations\\/([^/]+)\\/publishers\\/google\\/models\\/([^:]+):(.+)$/;\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 */\nexport function normalizePathLabel(pathname: string): string {\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}\n const geminiMatch = pathname.match(GEMINI_RE);\n if (geminiMatch) {\n return `/v1beta/models/{model}:${geminiMatch[2]}`;\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 return `/v1/projects/{p}/locations/{l}/publishers/google/models/{m}:${vertexMatch[4]}`;\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 // Static path — return as-is\n return pathname;\n}\n"],"mappings":"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"}
@@ -5,6 +5,7 @@ async function writeNDJSONStream(res, chunks, options) {
5
5
  const opts = options ?? {};
6
6
  const latency = opts.latency ?? 0;
7
7
  const profile = opts.streamingProfile;
8
+ const { recordedTimings, replaySpeed } = opts;
8
9
  const signal = opts.signal;
9
10
  const onChunkSent = opts.onChunkSent;
10
11
  if (res.writableEnded) return true;
@@ -13,7 +14,7 @@ async function writeNDJSONStream(res, chunks, options) {
13
14
  res.setHeader("Connection", "keep-alive");
14
15
  let chunkIndex = 0;
15
16
  for (const chunk of chunks) {
16
- const chunkDelay = require_sse_writer.calculateDelay(chunkIndex, profile, latency);
17
+ const chunkDelay = require_sse_writer.calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);
17
18
  if (chunkDelay > 0) await require_sse_writer.delay(chunkDelay, signal);
18
19
  if (signal?.aborted) return false;
19
20
  if (res.writableEnded) return true;
@@ -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 } from \"./types.js\";\nimport { delay, calculateDelay } from \"./sse-writer.js\";\n\nexport interface NDJSONStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\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 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);\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":";;;AAkBA,eAAsB,kBACpB,KACA,QACA,SACkB;CAClB,MAAM,OAAO,WAAW,EAAE;CAC1B,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,UAAU,KAAK;CACrB,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,QAAQ;AAC/D,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":["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,4 +1,4 @@
1
- import { StreamingProfile } from "./types.cjs";
1
+ import { RecordedTimings, StreamingProfile } from "./types.cjs";
2
2
  import * as http$1 from "node:http";
3
3
 
4
4
  //#region src/ndjson-writer.d.ts
@@ -6,12 +6,13 @@ import * as http$1 from "node:http";
6
6
  interface NDJSONStreamOptions {
7
7
  latency?: number;
8
8
  streamingProfile?: StreamingProfile;
9
+ recordedTimings?: RecordedTimings;
10
+ replaySpeed?: number;
9
11
  signal?: AbortSignal;
10
12
  onChunkSent?: () => void;
11
13
  }
12
14
  declare function writeNDJSONStream(res: http$1.ServerResponse, chunks: object[], options?: NDJSONStreamOptions): Promise<boolean>;
13
15
  //# sourceMappingURL=ndjson-writer.d.ts.map
14
-
15
16
  //#endregion
16
17
  export { NDJSONStreamOptions, writeNDJSONStream };
17
18
  //# sourceMappingURL=ndjson-writer.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ndjson-writer.d.cts","names":[],"sources":["../src/ndjson-writer.ts"],"sourcesContent":[],"mappings":";;;;;AAcsB,UAHL,mBAAA,CAGK;EAIA,OAAA,CAAA,EAAA,MAAA;EAAiB,gBAAA,CAAA,EALlB,gBAKkB;QAChC,CAAA,EALI,WAKC;aAEA,CAAA,EAAA,GAAA,GAAA,IAAA;;AACF,iBAJY,iBAAA,CAIZ,GAAA,EAHH,MAAA,CAAK,cAGF,EAAA,MAAA,EAAA,MAAA,EAAA,EAAA,OAAA,CAAA,EADE,mBACF,CAAA,EAAP,OAAO,CAAA,OAAA,CAAA"}
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,4 +1,4 @@
1
- import { StreamingProfile } from "./types.js";
1
+ import { RecordedTimings, StreamingProfile } from "./types.js";
2
2
  import * as http$1 from "node:http";
3
3
 
4
4
  //#region src/ndjson-writer.d.ts
@@ -6,12 +6,13 @@ import * as http$1 from "node:http";
6
6
  interface NDJSONStreamOptions {
7
7
  latency?: number;
8
8
  streamingProfile?: StreamingProfile;
9
+ recordedTimings?: RecordedTimings;
10
+ replaySpeed?: number;
9
11
  signal?: AbortSignal;
10
12
  onChunkSent?: () => void;
11
13
  }
12
14
  declare function writeNDJSONStream(res: http$1.ServerResponse, chunks: object[], options?: NDJSONStreamOptions): Promise<boolean>;
13
15
  //# sourceMappingURL=ndjson-writer.d.ts.map
14
-
15
16
  //#endregion
16
17
  export { NDJSONStreamOptions, writeNDJSONStream };
17
18
  //# sourceMappingURL=ndjson-writer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ndjson-writer.d.ts","names":[],"sources":["../src/ndjson-writer.ts"],"sourcesContent":[],"mappings":";;;;;AAcsB,UAHL,mBAAA,CAGK;EAIA,OAAA,CAAA,EAAA,MAAA;EAAiB,gBAAA,CAAA,EALlB,gBAKkB;QAChC,CAAA,EALI,WAKC;aAEA,CAAA,EAAA,GAAA,GAAA,IAAA;;AACF,iBAJY,iBAAA,CAIZ,GAAA,EAHH,MAAA,CAAK,cAGF,EAAA,MAAA,EAAA,MAAA,EAAA,EAAA,OAAA,CAAA,EADE,mBACF,CAAA,EAAP,OAAO,CAAA,OAAA,CAAA"}
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"}
@@ -5,6 +5,7 @@ async function writeNDJSONStream(res, chunks, options) {
5
5
  const opts = options ?? {};
6
6
  const latency = opts.latency ?? 0;
7
7
  const profile = opts.streamingProfile;
8
+ const { recordedTimings, replaySpeed } = opts;
8
9
  const signal = opts.signal;
9
10
  const onChunkSent = opts.onChunkSent;
10
11
  if (res.writableEnded) return true;
@@ -13,7 +14,7 @@ async function writeNDJSONStream(res, chunks, options) {
13
14
  res.setHeader("Connection", "keep-alive");
14
15
  let chunkIndex = 0;
15
16
  for (const chunk of chunks) {
16
- const chunkDelay = calculateDelay(chunkIndex, profile, latency);
17
+ const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);
17
18
  if (chunkDelay > 0) await delay(chunkDelay, signal);
18
19
  if (signal?.aborted) return false;
19
20
  if (res.writableEnded) return true;
@@ -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 } from \"./types.js\";\nimport { delay, calculateDelay } from \"./sse-writer.js\";\n\nexport interface NDJSONStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\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 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);\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":";;;AAkBA,eAAsB,kBACpB,KACA,QACA,SACkB;CAClB,MAAM,OAAO,WAAW,EAAE;CAC1B,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,UAAU,KAAK;CACrB,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,QAAQ;AAC/D,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 { 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"}