@copilotkit/aimock 1.41.0 → 1.42.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 (215) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +12 -1
  4. package/README.md +2 -1
  5. package/dist/a2a-mock.cjs +18 -11
  6. package/dist/a2a-mock.cjs.map +1 -1
  7. package/dist/a2a-mock.d.cts +1 -0
  8. package/dist/a2a-mock.d.cts.map +1 -1
  9. package/dist/a2a-mock.d.ts +1 -0
  10. package/dist/a2a-mock.d.ts.map +1 -1
  11. package/dist/a2a-mock.js +18 -11
  12. package/dist/a2a-mock.js.map +1 -1
  13. package/dist/agui-handler.d.ts.map +1 -1
  14. package/dist/agui-mock.cjs +2 -0
  15. package/dist/agui-mock.cjs.map +1 -1
  16. package/dist/agui-mock.d.cts.map +1 -1
  17. package/dist/agui-mock.d.ts.map +1 -1
  18. package/dist/agui-mock.js +2 -0
  19. package/dist/agui-mock.js.map +1 -1
  20. package/dist/bedrock-converse.cjs +36 -2
  21. package/dist/bedrock-converse.cjs.map +1 -1
  22. package/dist/bedrock-converse.d.cts.map +1 -1
  23. package/dist/bedrock-converse.d.ts.map +1 -1
  24. package/dist/bedrock-converse.js +37 -3
  25. package/dist/bedrock-converse.js.map +1 -1
  26. package/dist/bedrock.cjs +50 -13
  27. package/dist/bedrock.cjs.map +1 -1
  28. package/dist/bedrock.d.cts.map +1 -1
  29. package/dist/bedrock.d.ts.map +1 -1
  30. package/dist/bedrock.js +51 -14
  31. package/dist/bedrock.js.map +1 -1
  32. package/dist/byteplus-video.cjs +2 -2
  33. package/dist/byteplus-video.cjs.map +1 -1
  34. package/dist/byteplus-video.d.cts.map +1 -1
  35. package/dist/byteplus-video.d.ts.map +1 -1
  36. package/dist/byteplus-video.js +2 -2
  37. package/dist/byteplus-video.js.map +1 -1
  38. package/dist/chaos.cjs +36 -7
  39. package/dist/chaos.cjs.map +1 -1
  40. package/dist/chaos.d.cts +8 -3
  41. package/dist/chaos.d.cts.map +1 -1
  42. package/dist/chaos.d.ts +8 -3
  43. package/dist/chaos.d.ts.map +1 -1
  44. package/dist/chaos.js +36 -8
  45. package/dist/chaos.js.map +1 -1
  46. package/dist/cohere.cjs +58 -5
  47. package/dist/cohere.cjs.map +1 -1
  48. package/dist/cohere.d.cts.map +1 -1
  49. package/dist/cohere.d.ts.map +1 -1
  50. package/dist/cohere.js +59 -6
  51. package/dist/cohere.js.map +1 -1
  52. package/dist/config-loader.d.ts.map +1 -1
  53. package/dist/elevenlabs-audio.cjs +36 -2
  54. package/dist/elevenlabs-audio.cjs.map +1 -1
  55. package/dist/elevenlabs-audio.d.cts.map +1 -1
  56. package/dist/elevenlabs-audio.d.ts.map +1 -1
  57. package/dist/elevenlabs-audio.js +37 -3
  58. package/dist/elevenlabs-audio.js.map +1 -1
  59. package/dist/embeddings.cjs +58 -3
  60. package/dist/embeddings.cjs.map +1 -1
  61. package/dist/embeddings.d.cts.map +1 -1
  62. package/dist/embeddings.d.ts.map +1 -1
  63. package/dist/embeddings.js +59 -4
  64. package/dist/embeddings.js.map +1 -1
  65. package/dist/fal-audio.cjs +38 -2
  66. package/dist/fal-audio.cjs.map +1 -1
  67. package/dist/fal-audio.d.cts.map +1 -1
  68. package/dist/fal-audio.d.ts.map +1 -1
  69. package/dist/fal-audio.js +39 -3
  70. package/dist/fal-audio.js.map +1 -1
  71. package/dist/fixture-loader.cjs +0 -0
  72. package/dist/fixture-loader.cjs.map +1 -1
  73. package/dist/fixture-loader.d.cts +9 -1
  74. package/dist/fixture-loader.d.cts.map +1 -1
  75. package/dist/fixture-loader.d.ts +9 -1
  76. package/dist/fixture-loader.d.ts.map +1 -1
  77. package/dist/fixture-loader.js +0 -0
  78. package/dist/fixture-loader.js.map +1 -1
  79. package/dist/gemini-embeddings.cjs +19 -1
  80. package/dist/gemini-embeddings.cjs.map +1 -1
  81. package/dist/gemini-embeddings.js +20 -2
  82. package/dist/gemini-embeddings.js.map +1 -1
  83. package/dist/gemini-interactions.cjs +15 -1
  84. package/dist/gemini-interactions.cjs.map +1 -1
  85. package/dist/gemini-interactions.d.cts.map +1 -1
  86. package/dist/gemini-interactions.d.ts.map +1 -1
  87. package/dist/gemini-interactions.js +16 -2
  88. package/dist/gemini-interactions.js.map +1 -1
  89. package/dist/gemini.cjs +70 -6
  90. package/dist/gemini.cjs.map +1 -1
  91. package/dist/gemini.d.cts.map +1 -1
  92. package/dist/gemini.d.ts.map +1 -1
  93. package/dist/gemini.js +71 -7
  94. package/dist/gemini.js.map +1 -1
  95. package/dist/grok-video.cjs +2 -2
  96. package/dist/grok-video.cjs.map +1 -1
  97. package/dist/grok-video.d.cts.map +1 -1
  98. package/dist/grok-video.d.ts.map +1 -1
  99. package/dist/grok-video.js +2 -2
  100. package/dist/grok-video.js.map +1 -1
  101. package/dist/helpers.cjs +160 -5
  102. package/dist/helpers.cjs.map +1 -1
  103. package/dist/helpers.d.cts.map +1 -1
  104. package/dist/helpers.d.ts.map +1 -1
  105. package/dist/helpers.js +152 -6
  106. package/dist/helpers.js.map +1 -1
  107. package/dist/images.cjs +22 -4
  108. package/dist/images.cjs.map +1 -1
  109. package/dist/images.d.cts.map +1 -1
  110. package/dist/images.d.ts.map +1 -1
  111. package/dist/images.js +23 -5
  112. package/dist/images.js.map +1 -1
  113. package/dist/llmock.cjs +15 -0
  114. package/dist/llmock.cjs.map +1 -1
  115. package/dist/llmock.d.cts +12 -0
  116. package/dist/llmock.d.cts.map +1 -1
  117. package/dist/llmock.d.ts +12 -0
  118. package/dist/llmock.d.ts.map +1 -1
  119. package/dist/llmock.js +16 -1
  120. package/dist/llmock.js.map +1 -1
  121. package/dist/mcp-mock.cjs +11 -0
  122. package/dist/mcp-mock.cjs.map +1 -1
  123. package/dist/mcp-mock.d.cts.map +1 -1
  124. package/dist/mcp-mock.d.ts.map +1 -1
  125. package/dist/mcp-mock.js +11 -0
  126. package/dist/mcp-mock.js.map +1 -1
  127. package/dist/messages.cjs +18 -1
  128. package/dist/messages.cjs.map +1 -1
  129. package/dist/messages.d.cts.map +1 -1
  130. package/dist/messages.d.ts.map +1 -1
  131. package/dist/messages.js +19 -2
  132. package/dist/messages.js.map +1 -1
  133. package/dist/moderation.cjs +67 -3
  134. package/dist/moderation.cjs.map +1 -1
  135. package/dist/moderation.d.cts +1 -0
  136. package/dist/moderation.d.cts.map +1 -1
  137. package/dist/moderation.d.ts +1 -0
  138. package/dist/moderation.d.ts.map +1 -1
  139. package/dist/moderation.js +68 -4
  140. package/dist/moderation.js.map +1 -1
  141. package/dist/ollama.cjs +75 -5
  142. package/dist/ollama.cjs.map +1 -1
  143. package/dist/ollama.d.cts.map +1 -1
  144. package/dist/ollama.d.ts.map +1 -1
  145. package/dist/ollama.js +76 -6
  146. package/dist/ollama.js.map +1 -1
  147. package/dist/openrouter-video.cjs +4 -4
  148. package/dist/openrouter-video.cjs.map +1 -1
  149. package/dist/openrouter-video.d.cts.map +1 -1
  150. package/dist/openrouter-video.d.ts.map +1 -1
  151. package/dist/openrouter-video.js +4 -4
  152. package/dist/openrouter-video.js.map +1 -1
  153. package/dist/rerank.cjs +65 -2
  154. package/dist/rerank.cjs.map +1 -1
  155. package/dist/rerank.d.cts +1 -1
  156. package/dist/rerank.d.cts.map +1 -1
  157. package/dist/rerank.d.ts +1 -1
  158. package/dist/rerank.d.ts.map +1 -1
  159. package/dist/rerank.js +66 -3
  160. package/dist/rerank.js.map +1 -1
  161. package/dist/responses.cjs +18 -1
  162. package/dist/responses.cjs.map +1 -1
  163. package/dist/responses.d.cts.map +1 -1
  164. package/dist/responses.d.ts.map +1 -1
  165. package/dist/responses.js +19 -2
  166. package/dist/responses.js.map +1 -1
  167. package/dist/search.cjs +67 -3
  168. package/dist/search.cjs.map +1 -1
  169. package/dist/search.d.cts +1 -0
  170. package/dist/search.d.cts.map +1 -1
  171. package/dist/search.d.ts +1 -0
  172. package/dist/search.d.ts.map +1 -1
  173. package/dist/search.js +68 -4
  174. package/dist/search.js.map +1 -1
  175. package/dist/server.cjs +317 -13
  176. package/dist/server.cjs.map +1 -1
  177. package/dist/server.d.cts.map +1 -1
  178. package/dist/server.d.ts.map +1 -1
  179. package/dist/server.js +320 -16
  180. package/dist/server.js.map +1 -1
  181. package/dist/speech.cjs +18 -1
  182. package/dist/speech.cjs.map +1 -1
  183. package/dist/speech.d.cts.map +1 -1
  184. package/dist/speech.d.ts.map +1 -1
  185. package/dist/speech.js +19 -2
  186. package/dist/speech.js.map +1 -1
  187. package/dist/transcription.cjs +21 -2
  188. package/dist/transcription.cjs.map +1 -1
  189. package/dist/transcription.d.cts.map +1 -1
  190. package/dist/transcription.d.ts.map +1 -1
  191. package/dist/transcription.js +21 -2
  192. package/dist/transcription.js.map +1 -1
  193. package/dist/types.d.cts +39 -2
  194. package/dist/types.d.cts.map +1 -1
  195. package/dist/types.d.ts +39 -2
  196. package/dist/types.d.ts.map +1 -1
  197. package/dist/vector-mock.cjs +88 -0
  198. package/dist/vector-mock.cjs.map +1 -1
  199. package/dist/vector-mock.d.cts.map +1 -1
  200. package/dist/vector-mock.d.ts.map +1 -1
  201. package/dist/vector-mock.js +89 -1
  202. package/dist/vector-mock.js.map +1 -1
  203. package/dist/veo-video.cjs +2 -2
  204. package/dist/veo-video.cjs.map +1 -1
  205. package/dist/veo-video.d.cts.map +1 -1
  206. package/dist/veo-video.d.ts.map +1 -1
  207. package/dist/veo-video.js +2 -2
  208. package/dist/veo-video.js.map +1 -1
  209. package/dist/video.cjs +19 -2
  210. package/dist/video.cjs.map +1 -1
  211. package/dist/video.d.cts.map +1 -1
  212. package/dist/video.d.ts.map +1 -1
  213. package/dist/video.js +20 -3
  214. package/dist/video.js.map +1 -1
  215. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"gemini.js","names":[],"sources":["../src/gemini.ts"],"sourcesContent":["/**\n * Google Gemini GenerateContent API support.\n *\n * Translates incoming Gemini requests into the ChatCompletionRequest format\n * used by the fixture router, and converts fixture responses back into the\n * Gemini GenerateContent streaming (or non-streaming) format.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n AudioResponse,\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n FixtureBlock,\n HandlerDefaults,\n RecordedTimings,\n RecordProviderKey,\n ResponseOverrides,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n isAudioResponse,\n extractOverrides,\n formatToMime,\n flattenHeaders,\n getContext,\n getTestId,\n resolveFixtureBlocks,\n resolveResponse,\n resolveStrictMode,\n resolveReasoningForModel,\n strictOverrideField,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic, recordMatchOptions } 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// ─── Gemini request types ───────────────────────────────────────────────────\n\ninterface GeminiPart {\n text?: string;\n thought?: boolean;\n functionCall?: { name: string; args: Record<string, unknown>; id?: string };\n functionResponse?: { name: string; response: unknown; id?: string };\n inlineData?: { mimeType: string; data: string };\n}\n\ninterface GeminiContent {\n role?: string;\n parts: GeminiPart[];\n}\n\ninterface GeminiFunctionDeclaration {\n name: string;\n description?: string;\n parameters?: object;\n}\n\ninterface GeminiToolDef {\n functionDeclarations?: GeminiFunctionDeclaration[];\n}\n\ninterface GeminiRequest {\n contents?: GeminiContent[];\n systemInstruction?: GeminiContent;\n tools?: GeminiToolDef[];\n generationConfig?: {\n temperature?: number;\n maxOutputTokens?: number;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\n// ─── Input conversion: Gemini → ChatCompletions messages ────────────────────\n\nexport function geminiToCompletionRequest(\n req: GeminiRequest,\n model: string,\n stream: boolean,\n): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n // systemInstruction → system message\n if (req.systemInstruction) {\n const text = req.systemInstruction.parts\n .filter((p) => p.text !== undefined)\n .map((p) => p.text!)\n .join(\"\");\n if (text) {\n messages.push({ role: \"system\", content: text });\n }\n }\n\n if (req.contents) {\n let callCounter = 0;\n for (const content of req.contents) {\n const role = content.role ?? \"user\";\n\n if (role === \"user\") {\n // Check for functionResponse parts\n const funcResponses = content.parts.filter((p) => p.functionResponse);\n const textParts = content.parts.filter((p) => p.text !== undefined && !p.thought);\n\n if (funcResponses.length > 0) {\n // functionResponse → tool message; match IDs from the preceding assistant's tool_calls\n const lastAssistant = [...messages]\n .reverse()\n .find((m) => m.role === \"assistant\" && m.tool_calls);\n const matchedToolCallIds = new Set<string>();\n for (const part of funcResponses) {\n const matchingCall = lastAssistant?.tool_calls?.find(\n (tc) =>\n tc.function.name === part.functionResponse!.name && !matchedToolCallIds.has(tc.id),\n );\n if (matchingCall) matchedToolCallIds.add(matchingCall.id);\n const toolCallId =\n matchingCall?.id ?? `call_gemini_${part.functionResponse!.name}_${callCounter++}`;\n messages.push({\n role: \"tool\",\n content:\n typeof part.functionResponse!.response === \"string\"\n ? part.functionResponse!.response\n : JSON.stringify(part.functionResponse!.response),\n tool_call_id: toolCallId,\n });\n }\n // Any text parts alongside → user message\n if (textParts.length > 0) {\n messages.push({\n role: \"user\",\n content: textParts.map((p) => p.text!).join(\"\"),\n });\n }\n } else {\n // Regular user text\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({ role: \"user\", content: text });\n }\n } else if (role === \"model\") {\n // Check for functionCall parts\n const funcCalls = content.parts.filter((p) => p.functionCall);\n const textParts = content.parts.filter((p) => p.text !== undefined && !p.thought);\n\n if (funcCalls.length > 0) {\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({\n role: \"assistant\",\n content: text || null,\n tool_calls: funcCalls.map((fc, i) => ({\n id: fc.functionCall!.id ?? `call_gemini_${fc.functionCall!.name}_${i}`,\n type: \"function\" as const,\n function: {\n name: fc.functionCall!.name,\n arguments: JSON.stringify(fc.functionCall!.args ?? {}),\n },\n })),\n });\n } else {\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({ role: \"assistant\", content: text });\n }\n }\n // Unrecognized roles (not \"user\" or \"model\") are silently dropped.\n // Gemini only defines \"user\" and \"model\"; any other value indicates\n // a malformed request or an unsupported future role.\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n const decls = req.tools.flatMap((t) => t.functionDeclarations ?? []);\n if (decls.length > 0) {\n tools = decls.map((d) => ({\n type: \"function\" as const,\n function: {\n name: d.name,\n description: d.description,\n parameters: d.parameters,\n },\n }));\n }\n }\n\n return {\n model,\n messages,\n stream,\n temperature: req.generationConfig?.temperature,\n max_tokens: req.generationConfig?.maxOutputTokens,\n top_p: req.generationConfig?.topP as number | undefined,\n top_k: req.generationConfig?.topK as number | undefined,\n tools,\n };\n}\n\n// ─── Response building: fixture → Gemini format ─────────────────────────────\n\nfunction geminiFinishReason(finishReason: string | undefined, defaultReason: string): string {\n if (!finishReason) return defaultReason;\n if (finishReason === \"stop\") return \"STOP\";\n if (finishReason === \"tool_calls\") return \"FUNCTION_CALL\";\n if (finishReason === \"length\") return \"MAX_TOKENS\";\n if (finishReason === \"content_filter\") return \"SAFETY\";\n // Pass through unrecognized values as-is\n return finishReason;\n}\n\nfunction geminiUsageMetadata(overrides?: ResponseOverrides): {\n promptTokenCount: number;\n candidatesTokenCount: number;\n totalTokenCount: number;\n} {\n if (!overrides?.usage)\n return { promptTokenCount: 0, candidatesTokenCount: 0, totalTokenCount: 0 };\n const prompt =\n overrides.usage.promptTokenCount ??\n overrides.usage.prompt_tokens ??\n overrides.usage.input_tokens ??\n 0;\n const candidates =\n overrides.usage.candidatesTokenCount ??\n overrides.usage.completion_tokens ??\n overrides.usage.output_tokens ??\n 0;\n const total = overrides.usage.totalTokenCount ?? prompt + candidates;\n return {\n promptTokenCount: prompt,\n candidatesTokenCount: candidates,\n totalTokenCount: total,\n };\n}\n\ninterface GeminiResponseChunk {\n candidates: {\n content: { role: string; parts: GeminiPart[] };\n finishReason?: string;\n index: number;\n }[];\n usageMetadata?: {\n promptTokenCount: number;\n candidatesTokenCount: number;\n totalTokenCount: number;\n };\n}\n\nfunction buildGeminiTextStreamChunks(\n content: string,\n chunkSize: number,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk[] {\n const chunks: GeminiResponseChunk[] = [];\n const effectiveFinish = geminiFinishReason(overrides?.finishReason, \"STOP\");\n const usage = geminiUsageMetadata(overrides);\n\n // Reasoning chunks (thought: true)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice, thought: true }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n // Content chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n const isLast = i + chunkSize >= content.length;\n const chunk: GeminiResponseChunk = {\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice }] },\n index: 0,\n ...(isLast ? { finishReason: effectiveFinish } : {}),\n },\n ],\n ...(isLast ? { usageMetadata: usage } : {}),\n };\n chunks.push(chunk);\n }\n\n // Handle empty content\n if (content.length === 0) {\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"\" }] },\n finishReason: effectiveFinish,\n index: 0,\n },\n ],\n usageMetadata: usage,\n });\n }\n\n return chunks;\n}\n\nfunction parseToolCallPart(tc: ToolCall, logger: Logger): GeminiPart {\n let argsObj: Record<string, unknown>;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\") as Record<string, unknown>;\n } catch {\n logger.warn(`Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`);\n argsObj = {};\n }\n // Surface the fixture's tool_call.id on the Gemini functionCall response\n // so clients can preserve it across the round-trip and any\n // toolCallId-keyed follow-up fixtures match. Pairs with v1.23.1's\n // INGEST-direction fix (#196) which preserves the id when aimock parses\n // an incoming Gemini request — that fix only helps if the id was in the\n // response body to begin with. Without this egress fix, aimock emits\n // `{ functionCall: { name, args } }` (no id), so even clients that\n // diligently preserve `functionCall.id` across the round-trip never see\n // an id to preserve. Backward-compatible: fixtures that don't pin a\n // tc.id continue to serialize without one (the ingest path's fallback\n // generator handles those).\n const functionCall: GeminiPart[\"functionCall\"] = {\n name: tc.name,\n args: argsObj,\n };\n if (tc.id) {\n functionCall.id = tc.id;\n }\n return { functionCall };\n}\n\nfunction buildGeminiToolCallStreamChunks(\n toolCalls: ToolCall[],\n chunkSize: number,\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk[] {\n const chunks: GeminiResponseChunk[] = [];\n\n // Reasoning chunks (thought: true) — mirror the content+tool / text paths so a\n // tool-only fixture on a reasoning-capable model still emits its thought channel.\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice, thought: true }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n // Gemini sends all tool calls in a single response chunk\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n });\n\n return chunks;\n}\n\n// Non-streaming response builders\n\nfunction buildGeminiTextResponse(\n content: string,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk {\n const parts: GeminiPart[] = [];\n if (reasoning) {\n parts.push({ text: reasoning, thought: true });\n }\n parts.push({ text: content });\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"STOP\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n };\n}\n\nfunction buildGeminiToolCallResponse(\n toolCalls: ToolCall[],\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk {\n const parts: GeminiPart[] = [];\n if (reasoning) {\n parts.push({ text: reasoning, thought: true });\n }\n parts.push(...toolCalls.map((tc) => parseToolCallPart(tc, logger)));\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n };\n}\n\nfunction buildGeminiContentWithToolCallsStreamChunks(\n content: string,\n toolCalls: ToolCall[],\n chunkSize: number,\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n blocks?: FixtureBlock[],\n): GeminiResponseChunk[] {\n const chunks: GeminiResponseChunk[] = [];\n\n // Reasoning chunks (thought: true)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice, thought: true }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n if (blocks && blocks.length > 0) {\n // NEW path (#274): stream chunks whose parts follow the blocks' ARRAY ORDER,\n // so a tool-first / interleaved fixture emits its functionCall part before\n // its text part. Gemini's ordered `parts` make this fully expressible. The\n // terminal block carries the finishReason regardless of its type. Legacy\n // fixtures (no `blocks`) never enter here — see the else branch below.\n const resolved = resolveFixtureBlocks(blocks);\n resolved.forEach((block, i) => {\n const isLast = i === resolved.length - 1;\n const finishReason = isLast\n ? geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\")\n : undefined;\n if (block.type === \"toolCall\") {\n const part = parseToolCallPart(\n { name: block.name, arguments: block.arguments, id: block.id },\n logger,\n );\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [part] },\n ...(finishReason ? { finishReason } : {}),\n index: 0,\n },\n ],\n ...(isLast ? { usageMetadata: geminiUsageMetadata(overrides) } : {}),\n });\n } else {\n const text = block.text;\n if (text.length === 0) {\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"\" }] },\n ...(finishReason ? { finishReason } : {}),\n index: 0,\n },\n ],\n ...(isLast ? { usageMetadata: geminiUsageMetadata(overrides) } : {}),\n });\n } else {\n for (let j = 0; j < text.length; j += chunkSize) {\n const slice = text.slice(j, j + chunkSize);\n const lastSlice = j + chunkSize >= text.length;\n const sliceFinish = isLast && lastSlice ? finishReason : undefined;\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice }] },\n ...(sliceFinish ? { finishReason: sliceFinish } : {}),\n index: 0,\n },\n ],\n ...(isLast && lastSlice ? { usageMetadata: geminiUsageMetadata(overrides) } : {}),\n });\n }\n }\n }\n });\n\n return chunks;\n }\n\n if (content.length === 0) {\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"\" }] },\n index: 0,\n },\n ],\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n });\n\n return chunks;\n}\n\nfunction buildGeminiContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n blocks?: FixtureBlock[],\n): GeminiResponseChunk {\n const parts: GeminiPart[] = [];\n if (reasoning) {\n parts.push({ text: reasoning, thought: true });\n }\n\n if (blocks && blocks.length > 0) {\n // NEW PATH: the non-streaming `parts[]` array is positionally observable, so\n // emit parts in the fixture's ARRAY ORDER (after any leading thought part).\n // A toolCall block before a text block therefore yields a functionCall part\n // ahead of the text — matching the streaming path for the same `blocks`.\n const resolved = resolveFixtureBlocks(blocks);\n for (const block of resolved) {\n if (block.type === \"toolCall\") {\n parts.push(\n parseToolCallPart({ name: block.name, arguments: block.arguments, id: block.id }, logger),\n );\n } else {\n parts.push({ text: block.text });\n }\n }\n } else {\n // LEGACY PATH (unchanged): text part first, then functionCall parts.\n parts.push({ text: content });\n parts.push(...toolCalls.map((tc) => parseToolCallPart(tc, logger)));\n }\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n };\n}\n\n// ─── Audio response builders ────────────────────────────────────────────────\n\nfunction resolveAudioInlineData(audio: AudioResponse): { mimeType: string; data: string } {\n if (typeof audio.audio === \"string\") {\n return { mimeType: formatToMime(audio.format ?? \"mp3\"), data: audio.audio };\n }\n return {\n mimeType: audio.audio.contentType ?? \"audio/mpeg\",\n data: audio.audio.b64Json,\n };\n}\n\n// Build the ordered Gemini parts for an audio turn: inlineData audio first,\n// then any companion modalities (reasoning/thought, text content, tool calls)\n// the recorder preserved on the AudioResponse. Without this, a recorded turn\n// that interleaves audio with a functionCall and/or text silently drops the\n// companions on replay.\n// NOTE: audio companions are only re-emitted on this Gemini replay path because\n// `audioB64` collapse is currently Gemini-only — a cross-provider audio fixture\n// would not replay its companions.\n// `effReasoning` is the capability-gated reasoning string (already passed\n// through resolveReasoningForModel at the dispatch). When the requested model\n// is not reasoning-capable (and strict mode is on), it is undefined and the\n// companion `thought` part is suppressed — mirroring the text/content+tool\n// branches so the audio path does not replay reasoning a real Gemini model\n// would never emit.\nfunction buildGeminiAudioParts(\n audio: AudioResponse,\n logger: Logger,\n effReasoning: string | undefined,\n): GeminiPart[] {\n const inlineData = resolveAudioInlineData(audio);\n const parts: GeminiPart[] = [{ inlineData }];\n if (effReasoning) {\n parts.push({ text: effReasoning, thought: true });\n }\n if (audio.content) {\n parts.push({ text: audio.content });\n }\n if (audio.toolCalls?.length) {\n parts.push(...audio.toolCalls.map((tc) => parseToolCallPart(tc, logger)));\n }\n return parts;\n}\n\nfunction buildGeminiAudioResponse(\n audio: AudioResponse,\n logger: Logger,\n effReasoning: string | undefined,\n): GeminiResponseChunk {\n return {\n candidates: [\n {\n content: { role: \"model\", parts: buildGeminiAudioParts(audio, logger, effReasoning) },\n finishReason: audio.toolCalls?.length ? \"FUNCTION_CALL\" : \"STOP\",\n index: 0,\n },\n ],\n usageMetadata: { promptTokenCount: 0, candidatesTokenCount: 0, totalTokenCount: 0 },\n };\n}\n\nfunction buildGeminiAudioStreamChunks(\n audio: AudioResponse,\n logger: Logger,\n effReasoning: string | undefined,\n): GeminiResponseChunk[] {\n return [\n {\n candidates: [\n {\n content: { role: \"model\", parts: buildGeminiAudioParts(audio, logger, effReasoning) },\n finishReason: audio.toolCalls?.length ? \"FUNCTION_CALL\" : \"STOP\",\n index: 0,\n },\n ],\n usageMetadata: { promptTokenCount: 0, candidatesTokenCount: 0, totalTokenCount: 0 },\n },\n ];\n}\n\n// ─── SSE writer for Gemini streaming ────────────────────────────────────────\n\ninterface GeminiStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nasync function writeGeminiSSEStream(\n res: http.ServerResponse,\n chunks: GeminiResponseChunk[],\n optionsOrLatency?: number | GeminiStreamOptions,\n): Promise<boolean> {\n const opts: GeminiStreamOptions =\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 chunk of chunks) {\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 // Gemini uses data-only SSE (no event: prefix, no [DONE])\n res.write(`data: ${JSON.stringify(chunk)}\\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 handleGemini(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n model: string,\n streaming: boolean,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n providerKey: RecordProviderKey = \"gemini\",\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let geminiReq: GeminiRequest;\n try {\n geminiReq = JSON.parse(raw) as GeminiRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? `/v1beta/models/${model}:generateContent`,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: `Malformed JSON body: ${detail}`,\n code: 400,\n status: \"INVALID_ARGUMENT\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = geminiToCompletionRequest(geminiReq, model, streaming);\n completionReq._endpointType = \"chat\";\n completionReq._context = getContext(req);\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n // Record mode proxies on a miss to capture a fresh turn (see record gate\n // below), so keep turnIndex strict to prevent an earlier-turn fixture from\n // shadowing a longer request and skipping the new turn's recording.\n recordMatchOptions(!!defaults.record, defaults.logger),\n );\n const path = req.url ?? `/v1beta/models/${model}:generateContent`;\n\n if (fixture) {\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n logger.debug(`No fixture matched for request`);\n }\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\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,\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 strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n logger.error(strictNoMatchLogLine(req.method ?? \"POST\", path, skippedBySequenceOrTurn));\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n code: 503,\n status: \"UNAVAILABLE\",\n },\n }),\n );\n return;\n }\n\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n providerKey,\n path,\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,\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,\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 code: 404,\n status: \"NOT_FOUND\",\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 const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n // Gemini-style error format: { error: { code, message, status } }\n const geminiError = {\n error: {\n code: status,\n message: response.error.message,\n status: response.error.type ?? \"ERROR\",\n },\n };\n writeErrorResponse(res, status, JSON.stringify(geminiError), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n // Audio response\n if (isAudioResponse(response)) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n model,\n effectiveStrict,\n defaults.logger,\n );\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiAudioResponse(response, logger, effReasoning);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiAudioStreamChunks(response, logger, effReasoning);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n 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 // Content + tool calls response (must be checked before isTextResponse / isToolCallResponse)\n if (isContentWithToolCallsResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Gemini API — ignoring\");\n }\n const overrides = extractOverrides(response);\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n model,\n effectiveStrict,\n defaults.logger,\n );\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiContentWithToolCallsResponse(\n response.content ?? \"\",\n response.toolCalls ?? [],\n logger,\n effReasoning,\n overrides,\n response.blocks,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiContentWithToolCallsStreamChunks(\n response.content ?? \"\",\n response.toolCalls ?? [],\n chunkSize,\n logger,\n effReasoning,\n overrides,\n response.blocks,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Gemini API — ignoring\");\n }\n const overrides = extractOverrides(response);\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n model,\n effectiveStrict,\n defaults.logger,\n );\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiTextResponse(response.content, effReasoning, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiTextStreamChunks(\n response.content,\n chunkSize,\n effReasoning,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Gemini API — ignoring\");\n }\n const overrides = extractOverrides(response);\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n model,\n effectiveStrict,\n defaults.logger,\n );\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiToolCallResponse(response.toolCalls, logger, effReasoning, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiToolCallStreamChunks(\n response.toolCalls,\n chunkSize,\n logger,\n effReasoning,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n 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,\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 code: 500,\n status: \"INTERNAL\",\n },\n }),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"gemini.js","names":[],"sources":["../src/gemini.ts"],"sourcesContent":["/**\n * Google Gemini GenerateContent API support.\n *\n * Translates incoming Gemini requests into the ChatCompletionRequest format\n * used by the fixture router, and converts fixture responses back into the\n * Gemini GenerateContent streaming (or non-streaming) format.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n AudioResponse,\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n FixtureBlock,\n HandlerDefaults,\n RecordedTimings,\n RecordProviderKey,\n ResponseOverrides,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n isAudioResponse,\n extractOverrides,\n validateToolsField,\n formatToMime,\n flattenHeaders,\n isJsonObject,\n getContext,\n getTestId,\n resolveFixtureBlocks,\n resolveResponse,\n resolveStrictMode,\n resolveReasoningForModel,\n strictOverrideField,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic, recordMatchOptions } 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// ─── Gemini request types ───────────────────────────────────────────────────\n\ninterface GeminiPart {\n text?: string;\n thought?: boolean;\n functionCall?: { name: string; args: Record<string, unknown>; id?: string };\n functionResponse?: { name: string; response: unknown; id?: string };\n inlineData?: { mimeType: string; data: string };\n}\n\ninterface GeminiContent {\n role?: string;\n parts: GeminiPart[];\n}\n\ninterface GeminiFunctionDeclaration {\n name: string;\n description?: string;\n parameters?: object;\n}\n\ninterface GeminiToolDef {\n functionDeclarations?: GeminiFunctionDeclaration[];\n}\n\ninterface GeminiRequest {\n contents?: GeminiContent[];\n systemInstruction?: GeminiContent;\n tools?: GeminiToolDef[];\n generationConfig?: {\n temperature?: number;\n maxOutputTokens?: number;\n [key: string]: unknown;\n };\n [key: string]: unknown;\n}\n\n// ─── Input conversion: Gemini → ChatCompletions messages ────────────────────\n\n/**\n * Validate the shape of a Gemini `generateContent` body.\n *\n * Returns an error detail string when the shape is wrong, or null when\n * `geminiToCompletionRequest` can safely consume it. Wrong types here used\n * to throw inside the converter (`parts.filter` on a missing `parts`,\n * iterating the characters of a string `contents`, `.flatMap` on a string\n * `tools`) and surface as 500s. A present-but-part-less content or\n * systemInstruction is tolerated (treated as no text) rather than rejected.\n */\nfunction validateGeminiBody(req: GeminiRequest): string | null {\n if (req.contents !== undefined && req.contents !== null && !Array.isArray(req.contents)) {\n return \"contents must be an array\";\n }\n if (Array.isArray(req.contents)) {\n for (let i = 0; i < req.contents.length; i++) {\n const content = req.contents[i] as unknown;\n if (content === null || typeof content !== \"object\" || Array.isArray(content)) {\n return `contents[${i}] must be an object`;\n }\n const parts = (content as { parts?: unknown }).parts;\n if (parts !== undefined && parts !== null && !Array.isArray(parts)) {\n return `contents[${i}].parts must be an array`;\n }\n }\n }\n if (req.systemInstruction !== undefined && req.systemInstruction !== null) {\n if (typeof req.systemInstruction !== \"object\" || Array.isArray(req.systemInstruction)) {\n return \"systemInstruction must be an object\";\n }\n const parts = req.systemInstruction.parts as unknown;\n if (parts !== undefined && parts !== null && !Array.isArray(parts)) {\n return \"systemInstruction.parts must be an array\";\n }\n }\n return validateToolsField(req.tools);\n}\n\nexport function geminiToCompletionRequest(\n req: GeminiRequest,\n model: string,\n stream: boolean,\n): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n\n // systemInstruction → system message\n if (req.systemInstruction) {\n const text = (req.systemInstruction.parts ?? [])\n .filter((p) => p !== null && p !== undefined && p.text !== undefined)\n .map((p) => p.text!)\n .join(\"\");\n if (text) {\n messages.push({ role: \"system\", content: text });\n }\n }\n\n if (req.contents) {\n let callCounter = 0;\n for (const content of req.contents) {\n const role = content.role ?? \"user\";\n\n if (role === \"user\") {\n // Check for functionResponse parts\n const parts = content.parts ?? [];\n const funcResponses = parts.filter(\n (p) => p !== null && p !== undefined && p.functionResponse,\n );\n const textParts = parts.filter(\n (p) => p !== null && p !== undefined && p.text !== undefined && !p.thought,\n );\n\n if (funcResponses.length > 0) {\n // functionResponse → tool message; match IDs from the preceding assistant's tool_calls\n const lastAssistant = [...messages]\n .reverse()\n .find((m) => m.role === \"assistant\" && m.tool_calls);\n const matchedToolCallIds = new Set<string>();\n for (const part of funcResponses) {\n const matchingCall = lastAssistant?.tool_calls?.find(\n (tc) =>\n tc.function.name === part.functionResponse!.name && !matchedToolCallIds.has(tc.id),\n );\n if (matchingCall) matchedToolCallIds.add(matchingCall.id);\n const toolCallId =\n matchingCall?.id ?? `call_gemini_${part.functionResponse!.name}_${callCounter++}`;\n messages.push({\n role: \"tool\",\n content:\n typeof part.functionResponse!.response === \"string\"\n ? part.functionResponse!.response\n : JSON.stringify(part.functionResponse!.response),\n tool_call_id: toolCallId,\n });\n }\n // Any text parts alongside → user message\n if (textParts.length > 0) {\n messages.push({\n role: \"user\",\n content: textParts.map((p) => p.text!).join(\"\"),\n });\n }\n } else {\n // Regular user text\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({ role: \"user\", content: text });\n }\n } else if (role === \"model\") {\n // Check for functionCall parts\n const parts = content.parts ?? [];\n const funcCalls = parts.filter((p) => p !== null && p !== undefined && p.functionCall);\n const textParts = parts.filter(\n (p) => p !== null && p !== undefined && p.text !== undefined && !p.thought,\n );\n\n if (funcCalls.length > 0) {\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({\n role: \"assistant\",\n content: text || null,\n tool_calls: funcCalls.map((fc, i) => ({\n id: fc.functionCall!.id ?? `call_gemini_${fc.functionCall!.name}_${i}`,\n type: \"function\" as const,\n function: {\n name: fc.functionCall!.name,\n arguments: JSON.stringify(fc.functionCall!.args ?? {}),\n },\n })),\n });\n } else {\n const text = textParts.map((p) => p.text!).join(\"\");\n messages.push({ role: \"assistant\", content: text });\n }\n }\n // Unrecognized roles (not \"user\" or \"model\") are silently dropped.\n // Gemini only defines \"user\" and \"model\"; any other value indicates\n // a malformed request or an unsupported future role.\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n const decls = req.tools.flatMap((t) => t.functionDeclarations ?? []);\n if (decls.length > 0) {\n tools = decls.map((d) => ({\n type: \"function\" as const,\n function: {\n name: d.name,\n description: d.description,\n parameters: d.parameters,\n },\n }));\n }\n }\n\n return {\n model,\n messages,\n stream,\n temperature: req.generationConfig?.temperature,\n max_tokens: req.generationConfig?.maxOutputTokens,\n top_p: req.generationConfig?.topP as number | undefined,\n top_k: req.generationConfig?.topK as number | undefined,\n tools,\n };\n}\n\n// ─── Response building: fixture → Gemini format ─────────────────────────────\n\nfunction geminiFinishReason(finishReason: string | undefined, defaultReason: string): string {\n if (!finishReason) return defaultReason;\n if (finishReason === \"stop\") return \"STOP\";\n if (finishReason === \"tool_calls\") return \"FUNCTION_CALL\";\n if (finishReason === \"length\") return \"MAX_TOKENS\";\n if (finishReason === \"content_filter\") return \"SAFETY\";\n // Pass through unrecognized values as-is\n return finishReason;\n}\n\nfunction geminiUsageMetadata(overrides?: ResponseOverrides): {\n promptTokenCount: number;\n candidatesTokenCount: number;\n totalTokenCount: number;\n} {\n if (!overrides?.usage)\n return { promptTokenCount: 0, candidatesTokenCount: 0, totalTokenCount: 0 };\n const prompt =\n overrides.usage.promptTokenCount ??\n overrides.usage.prompt_tokens ??\n overrides.usage.input_tokens ??\n 0;\n const candidates =\n overrides.usage.candidatesTokenCount ??\n overrides.usage.completion_tokens ??\n overrides.usage.output_tokens ??\n 0;\n const total = overrides.usage.totalTokenCount ?? prompt + candidates;\n return {\n promptTokenCount: prompt,\n candidatesTokenCount: candidates,\n totalTokenCount: total,\n };\n}\n\ninterface GeminiResponseChunk {\n candidates: {\n content: { role: string; parts: GeminiPart[] };\n finishReason?: string;\n index: number;\n }[];\n usageMetadata?: {\n promptTokenCount: number;\n candidatesTokenCount: number;\n totalTokenCount: number;\n };\n}\n\nfunction buildGeminiTextStreamChunks(\n content: string,\n chunkSize: number,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk[] {\n const chunks: GeminiResponseChunk[] = [];\n const effectiveFinish = geminiFinishReason(overrides?.finishReason, \"STOP\");\n const usage = geminiUsageMetadata(overrides);\n\n // Reasoning chunks (thought: true)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice, thought: true }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n // Content chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n const isLast = i + chunkSize >= content.length;\n const chunk: GeminiResponseChunk = {\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice }] },\n index: 0,\n ...(isLast ? { finishReason: effectiveFinish } : {}),\n },\n ],\n ...(isLast ? { usageMetadata: usage } : {}),\n };\n chunks.push(chunk);\n }\n\n // Handle empty content\n if (content.length === 0) {\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"\" }] },\n finishReason: effectiveFinish,\n index: 0,\n },\n ],\n usageMetadata: usage,\n });\n }\n\n return chunks;\n}\n\nfunction parseToolCallPart(tc: ToolCall, logger: Logger): GeminiPart {\n let argsObj: Record<string, unknown>;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\") as Record<string, unknown>;\n } catch {\n logger.warn(`Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`);\n argsObj = {};\n }\n // Surface the fixture's tool_call.id on the Gemini functionCall response\n // so clients can preserve it across the round-trip and any\n // toolCallId-keyed follow-up fixtures match. Pairs with v1.23.1's\n // INGEST-direction fix (#196) which preserves the id when aimock parses\n // an incoming Gemini request — that fix only helps if the id was in the\n // response body to begin with. Without this egress fix, aimock emits\n // `{ functionCall: { name, args } }` (no id), so even clients that\n // diligently preserve `functionCall.id` across the round-trip never see\n // an id to preserve. Backward-compatible: fixtures that don't pin a\n // tc.id continue to serialize without one (the ingest path's fallback\n // generator handles those).\n const functionCall: GeminiPart[\"functionCall\"] = {\n name: tc.name,\n args: argsObj,\n };\n if (tc.id) {\n functionCall.id = tc.id;\n }\n return { functionCall };\n}\n\nfunction buildGeminiToolCallStreamChunks(\n toolCalls: ToolCall[],\n chunkSize: number,\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk[] {\n const chunks: GeminiResponseChunk[] = [];\n\n // Reasoning chunks (thought: true) — mirror the content+tool / text paths so a\n // tool-only fixture on a reasoning-capable model still emits its thought channel.\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice, thought: true }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n // Gemini sends all tool calls in a single response chunk\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n });\n\n return chunks;\n}\n\n// Non-streaming response builders\n\nfunction buildGeminiTextResponse(\n content: string,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk {\n const parts: GeminiPart[] = [];\n if (reasoning) {\n parts.push({ text: reasoning, thought: true });\n }\n parts.push({ text: content });\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"STOP\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n };\n}\n\nfunction buildGeminiToolCallResponse(\n toolCalls: ToolCall[],\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n): GeminiResponseChunk {\n const parts: GeminiPart[] = [];\n if (reasoning) {\n parts.push({ text: reasoning, thought: true });\n }\n parts.push(...toolCalls.map((tc) => parseToolCallPart(tc, logger)));\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n };\n}\n\nfunction buildGeminiContentWithToolCallsStreamChunks(\n content: string,\n toolCalls: ToolCall[],\n chunkSize: number,\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n blocks?: FixtureBlock[],\n): GeminiResponseChunk[] {\n const chunks: GeminiResponseChunk[] = [];\n\n // Reasoning chunks (thought: true)\n if (reasoning) {\n for (let i = 0; i < reasoning.length; i += chunkSize) {\n const slice = reasoning.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice, thought: true }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n if (blocks && blocks.length > 0) {\n // NEW path (#274): stream chunks whose parts follow the blocks' ARRAY ORDER,\n // so a tool-first / interleaved fixture emits its functionCall part before\n // its text part. Gemini's ordered `parts` make this fully expressible. The\n // terminal block carries the finishReason regardless of its type. Legacy\n // fixtures (no `blocks`) never enter here — see the else branch below.\n const resolved = resolveFixtureBlocks(blocks);\n resolved.forEach((block, i) => {\n const isLast = i === resolved.length - 1;\n const finishReason = isLast\n ? geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\")\n : undefined;\n if (block.type === \"toolCall\") {\n const part = parseToolCallPart(\n { name: block.name, arguments: block.arguments, id: block.id },\n logger,\n );\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [part] },\n ...(finishReason ? { finishReason } : {}),\n index: 0,\n },\n ],\n ...(isLast ? { usageMetadata: geminiUsageMetadata(overrides) } : {}),\n });\n } else {\n const text = block.text;\n if (text.length === 0) {\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"\" }] },\n ...(finishReason ? { finishReason } : {}),\n index: 0,\n },\n ],\n ...(isLast ? { usageMetadata: geminiUsageMetadata(overrides) } : {}),\n });\n } else {\n for (let j = 0; j < text.length; j += chunkSize) {\n const slice = text.slice(j, j + chunkSize);\n const lastSlice = j + chunkSize >= text.length;\n const sliceFinish = isLast && lastSlice ? finishReason : undefined;\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice }] },\n ...(sliceFinish ? { finishReason: sliceFinish } : {}),\n index: 0,\n },\n ],\n ...(isLast && lastSlice ? { usageMetadata: geminiUsageMetadata(overrides) } : {}),\n });\n }\n }\n }\n });\n\n return chunks;\n }\n\n if (content.length === 0) {\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"\" }] },\n index: 0,\n },\n ],\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: slice }] },\n index: 0,\n },\n ],\n });\n }\n }\n\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n });\n\n return chunks;\n}\n\nfunction buildGeminiContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n logger: Logger,\n reasoning?: string,\n overrides?: ResponseOverrides,\n blocks?: FixtureBlock[],\n): GeminiResponseChunk {\n const parts: GeminiPart[] = [];\n if (reasoning) {\n parts.push({ text: reasoning, thought: true });\n }\n\n if (blocks && blocks.length > 0) {\n // NEW PATH: the non-streaming `parts[]` array is positionally observable, so\n // emit parts in the fixture's ARRAY ORDER (after any leading thought part).\n // A toolCall block before a text block therefore yields a functionCall part\n // ahead of the text — matching the streaming path for the same `blocks`.\n const resolved = resolveFixtureBlocks(blocks);\n for (const block of resolved) {\n if (block.type === \"toolCall\") {\n parts.push(\n parseToolCallPart({ name: block.name, arguments: block.arguments, id: block.id }, logger),\n );\n } else {\n parts.push({ text: block.text });\n }\n }\n } else {\n // LEGACY PATH (unchanged): text part first, then functionCall parts.\n parts.push({ text: content });\n parts.push(...toolCalls.map((tc) => parseToolCallPart(tc, logger)));\n }\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: geminiFinishReason(overrides?.finishReason, \"FUNCTION_CALL\"),\n index: 0,\n },\n ],\n usageMetadata: geminiUsageMetadata(overrides),\n };\n}\n\n// ─── Audio response builders ────────────────────────────────────────────────\n\nfunction resolveAudioInlineData(audio: AudioResponse): { mimeType: string; data: string } {\n if (typeof audio.audio === \"string\") {\n return { mimeType: formatToMime(audio.format ?? \"mp3\"), data: audio.audio };\n }\n return {\n mimeType: audio.audio.contentType ?? \"audio/mpeg\",\n data: audio.audio.b64Json,\n };\n}\n\n// Build the ordered Gemini parts for an audio turn: inlineData audio first,\n// then any companion modalities (reasoning/thought, text content, tool calls)\n// the recorder preserved on the AudioResponse. Without this, a recorded turn\n// that interleaves audio with a functionCall and/or text silently drops the\n// companions on replay.\n// NOTE: audio companions are only re-emitted on this Gemini replay path because\n// `audioB64` collapse is currently Gemini-only — a cross-provider audio fixture\n// would not replay its companions.\n// `effReasoning` is the capability-gated reasoning string (already passed\n// through resolveReasoningForModel at the dispatch). When the requested model\n// is not reasoning-capable (and strict mode is on), it is undefined and the\n// companion `thought` part is suppressed — mirroring the text/content+tool\n// branches so the audio path does not replay reasoning a real Gemini model\n// would never emit.\nfunction buildGeminiAudioParts(\n audio: AudioResponse,\n logger: Logger,\n effReasoning: string | undefined,\n): GeminiPart[] {\n const inlineData = resolveAudioInlineData(audio);\n const parts: GeminiPart[] = [{ inlineData }];\n if (effReasoning) {\n parts.push({ text: effReasoning, thought: true });\n }\n if (audio.content) {\n parts.push({ text: audio.content });\n }\n if (audio.toolCalls?.length) {\n parts.push(...audio.toolCalls.map((tc) => parseToolCallPart(tc, logger)));\n }\n return parts;\n}\n\nfunction buildGeminiAudioResponse(\n audio: AudioResponse,\n logger: Logger,\n effReasoning: string | undefined,\n): GeminiResponseChunk {\n return {\n candidates: [\n {\n content: { role: \"model\", parts: buildGeminiAudioParts(audio, logger, effReasoning) },\n finishReason: audio.toolCalls?.length ? \"FUNCTION_CALL\" : \"STOP\",\n index: 0,\n },\n ],\n usageMetadata: { promptTokenCount: 0, candidatesTokenCount: 0, totalTokenCount: 0 },\n };\n}\n\nfunction buildGeminiAudioStreamChunks(\n audio: AudioResponse,\n logger: Logger,\n effReasoning: string | undefined,\n): GeminiResponseChunk[] {\n return [\n {\n candidates: [\n {\n content: { role: \"model\", parts: buildGeminiAudioParts(audio, logger, effReasoning) },\n finishReason: audio.toolCalls?.length ? \"FUNCTION_CALL\" : \"STOP\",\n index: 0,\n },\n ],\n usageMetadata: { promptTokenCount: 0, candidatesTokenCount: 0, totalTokenCount: 0 },\n },\n ];\n}\n\n// ─── SSE writer for Gemini streaming ────────────────────────────────────────\n\ninterface GeminiStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nasync function writeGeminiSSEStream(\n res: http.ServerResponse,\n chunks: GeminiResponseChunk[],\n optionsOrLatency?: number | GeminiStreamOptions,\n): Promise<boolean> {\n const opts: GeminiStreamOptions =\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 chunk of chunks) {\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 // Gemini uses data-only SSE (no event: prefix, no [DONE])\n res.write(`data: ${JSON.stringify(chunk)}\\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 handleGemini(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n model: string,\n streaming: boolean,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n providerKey: RecordProviderKey = \"gemini\",\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let geminiReq: GeminiRequest;\n try {\n geminiReq = JSON.parse(raw) as GeminiRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? `/v1beta/models/${model}:generateContent`,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: `Malformed JSON body: ${detail}`,\n code: 400,\n status: \"INVALID_ARGUMENT\",\n },\n }),\n );\n return;\n }\n\n // Reject non-object bodies (e.g. `null`) before touching fields —\n // otherwise the converter dereference throws a TypeError that surfaces as\n // a 500 instead of a 400.\n if (!isJsonObject(geminiReq)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? `/v1beta/models/${model}:generateContent`,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: \"Request body must be a JSON object\",\n code: 400,\n status: \"INVALID_ARGUMENT\",\n },\n }),\n );\n return;\n }\n\n // Reject wrong-typed fields before the converter dereferences them.\n const geminiShapeError = validateGeminiBody(geminiReq);\n if (geminiShapeError) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? `/v1beta/models/${model}:generateContent`,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n error: {\n message: `Invalid argument: ${geminiShapeError}`,\n code: 400,\n status: \"INVALID_ARGUMENT\",\n },\n }),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = geminiToCompletionRequest(geminiReq, model, streaming);\n completionReq._endpointType = \"chat\";\n completionReq._context = getContext(req);\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n // Record mode proxies on a miss to capture a fresh turn (see record gate\n // below), so keep turnIndex strict to prevent an earlier-turn fixture from\n // shadowing a longer request and skipping the new turn's recording.\n recordMatchOptions(!!defaults.record, defaults.logger),\n );\n const path = req.url ?? `/v1beta/models/${model}:generateContent`;\n\n if (fixture) {\n logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n logger.debug(`No fixture matched for request`);\n }\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n {\n method: req.method ?? \"POST\",\n path,\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 strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n logger.error(strictNoMatchLogLine(req.method ?? \"POST\", path, skippedBySequenceOrTurn));\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n error: {\n message: strictMessage,\n code: 503,\n status: \"UNAVAILABLE\",\n },\n }),\n );\n return;\n }\n\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n providerKey,\n path,\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,\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,\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 code: 404,\n status: \"NOT_FOUND\",\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 const replaySpeed = fixture.replaySpeed ?? defaults.replaySpeed;\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n // Gemini-style error format: { error: { code, message, status } }\n const geminiError = {\n error: {\n code: status,\n message: response.error.message,\n status: response.error.type ?? \"ERROR\",\n },\n };\n writeErrorResponse(res, status, JSON.stringify(geminiError), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n // Audio response\n if (isAudioResponse(response)) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n model,\n effectiveStrict,\n defaults.logger,\n );\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiAudioResponse(response, logger, effReasoning);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiAudioStreamChunks(response, logger, effReasoning);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n 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 // Content + tool calls response (must be checked before isTextResponse / isToolCallResponse)\n if (isContentWithToolCallsResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Gemini API — ignoring\");\n }\n const overrides = extractOverrides(response);\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n model,\n effectiveStrict,\n defaults.logger,\n );\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiContentWithToolCallsResponse(\n response.content ?? \"\",\n response.toolCalls ?? [],\n logger,\n effReasoning,\n overrides,\n response.blocks,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiContentWithToolCallsStreamChunks(\n response.content ?? \"\",\n response.toolCalls ?? [],\n chunkSize,\n logger,\n effReasoning,\n overrides,\n response.blocks,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Gemini API — ignoring\");\n }\n const overrides = extractOverrides(response);\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n model,\n effectiveStrict,\n defaults.logger,\n );\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiTextResponse(response.content, effReasoning, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiTextStreamChunks(\n response.content,\n chunkSize,\n effReasoning,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n replaySpeed,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\"webSearches in fixture response are not supported for Gemini API — ignoring\");\n }\n const overrides = extractOverrides(response);\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n const effReasoning = resolveReasoningForModel(\n response.reasoning,\n model,\n effectiveStrict,\n defaults.logger,\n );\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildGeminiToolCallResponse(response.toolCalls, logger, effReasoning, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiToolCallStreamChunks(\n response.toolCalls,\n chunkSize,\n logger,\n effReasoning,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n recordedTimings: fixture.recordedTimings,\n 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,\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 code: 500,\n status: \"INTERNAL\",\n },\n }),\n );\n}\n"],"mappings":"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"}
@@ -237,7 +237,7 @@ async function handleGrokVideoCreate(req, res, raw, fixtures, journal, defaults,
237
237
  const snippet = videoReq.prompt.slice(0, 80);
238
238
  defaults.logger.debug(`No fixture matched for request (model=${syntheticReq.model}, msg="${snippet}")`);
239
239
  }
240
- if (require_chaos.applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
240
+ if (require_chaos.applyChaos(res, fixture, defaults.chaos, req.headers, req.url, journal, {
241
241
  method,
242
242
  path,
243
243
  headers: require_helpers.flattenHeaders(req.headers),
@@ -373,7 +373,7 @@ async function handleGrokVideoStatus(req, res, id, fixtures, journal, defaults,
373
373
  setCorsHeaders(res);
374
374
  const path = req.url ?? `/v1/videos/${id}`;
375
375
  const method = req.method ?? "GET";
376
- if (require_chaos.applyChaos(res, null, defaults.chaos, req.headers, journal, {
376
+ if (require_chaos.applyChaos(res, null, defaults.chaos, req.headers, req.url, journal, {
377
377
  method,
378
378
  path,
379
379
  headers: require_helpers.flattenHeaders(req.headers),
@@ -1 +1 @@
1
- {"version":3,"file":"grok-video.cjs","names":["flattenHeaders","getContext","getTestId","matchFixtureDiagnostic","applyChaos","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","resolveResponse","isErrorResponse","serializeErrorResponse","isVideoResponse","crypto","resolveProgression","testIdSuffix","resolveUpstreamUrl","prepareEgressHeaders","upstreamTimeoutSignal","readEnvelopeText","buildFixtureMatch","persistFixture","sanitizeHeaderValue"],"sources":["../src/grok-video.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport crypto from \"node:crypto\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RecordConfig,\n VideoResponse,\n} from \"./types.js\";\nimport {\n isVideoResponse,\n isErrorResponse,\n serializeErrorResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { resolveProgression } from \"./fal.js\";\nimport {\n buildFixtureMatch,\n prepareEgressHeaders,\n persistFixture,\n sanitizeHeaderValue,\n} from \"./recorder.js\";\nimport { resolveUpstreamUrl } from \"./url.js\";\nimport { handleVideoStatus, type VideoStateMap } from \"./video.js\";\nimport { readEnvelopeText, testIdSuffix, upstreamTimeoutSignal } from \"./video-proxy-shared.js\";\n\n/**\n * xAI Grok Imagine async video lifecycle mock. Submit\n * `POST /v1/videos/generations` returns `{ request_id }`; status\n * `GET /v1/videos/{request_id}` polls `pending → done | failed | expired` with\n * a synthesized `progress`. Multipart submits are rejected with HTTP 400\n * BEFORE body parse (the real API is JSON-only). `video.url` is served AS-IS —\n * aimock does NOT proxy or capture video bytes. With `record.providers.grok`\n * configured an unmatched submit becomes a live interactive proxy (submit +\n * poll forwarded 1:1, eager fixture capture of url/duration/cost on terminal\n * status). Strict mode wins.\n *\n * Grok status shares the `/v1/videos/{id}` path with Sora: the dispatch does a\n * job-map-first lookup and falls through to the UNCHANGED Sora\n * `handleVideoStatus` on a miss (disjoint id namespaces → unambiguous).\n *\n * cost_in_usd_ticks ↔ USD: USD = ticks / 1e10 (ASSUMED). Record persists USD in\n * `video.cost`; replay reconstructs ticks = round(cost * 1e10).\n */\n\ninterface GrokVideoRequest {\n model?: string;\n prompt?: string;\n [key: string]: unknown;\n}\n\nconst DEFAULT_GROK_VIDEO_MODEL = \"grok-imagine-video\";\n\n// USD ←→ Grok cost_in_usd_ticks conversion (ASSUMED: 1e10 ticks per USD).\nconst GROK_USD_TICKS_PER_USD = 1e10;\n\n// Default placeholder when a completed fixture omits `video.url` — every other\n// coercion on this surface is documented; the real API always returns a url.\nconst GROK_DEFAULT_VIDEO_URL = \"https://cdn.x.ai/aimock-placeholder-video.mp4\";\n\n// ─── GrokVideoJobMap (TTL + bounded) ────────────────────────────────────────\n\nexport const GROK_VIDEO_MAX_ENTRIES = 10_000;\nconst GROK_VIDEO_TTL_MS = 3_600_000; // 1 hour\n\ntype GrokVideoStatus = \"pending\" | \"in_progress\" | \"completed\" | \"failed\";\n\ninterface GrokVideoReplayJob {\n kind: \"replay\";\n requestId: string;\n status: GrokVideoStatus;\n /** Number of status polls the caller has made against this job. */\n pollCount: number;\n /** Poll-count threshold for `pending → in_progress` transition. */\n pollsBeforeInProgress: number;\n /** Poll-count threshold for the transition to the terminal status. */\n pollsBeforeCompleted: number;\n /** The matched fixture's video object (terminal status, url, duration, cost, error). */\n video: VideoResponse[\"video\"];\n /** Latch for the empty-`error` authoring warn on failed polls (once per job). */\n emptyErrorWarned?: boolean;\n}\n\n/**\n * A job whose lifecycle is proxied live upstream (record mode, no fixture\n * matched at submit). Every client poll is forwarded 1:1 to\n * `upstreamPollingUrl`; when the upstream reports a terminal status (`done`/\n * `failed`) the entry is captured as a fixture and MUTATED into a terminal\n * GrokVideoReplayJob. `expired` (and any unrepresentable status) pass through\n * with a warn and keep proxying. Under `record.proxyOnly` nothing is ever\n * captured or mutated.\n */\ninterface GrokVideoRecordJob {\n kind: \"record\";\n /** The mock-issued requestId the client polls with. */\n requestId: string;\n /** Last upstream status relayed to the client. */\n status: GrokVideoStatus;\n /** Upstream's own request id (recorded as the fixture's video.id). */\n upstreamRequestId: string;\n /** Origin-validated absolute upstream URL proxied on every client poll. */\n upstreamPollingUrl: string;\n /** Match snapshot built at submit time (post requestTransform). */\n match: Fixture[\"match\"];\n /**\n * Set synchronously before the first await of the eager-capture sequence so\n * a concurrent terminal poll relays the upstream body instead of starting a\n * second capture/persist.\n */\n capturing?: boolean;\n}\n\nexport type GrokVideoJob = GrokVideoReplayJob | GrokVideoRecordJob;\n\ninterface GrokVideoEntry {\n job: GrokVideoJob;\n createdAt: number;\n}\n\n/**\n * Per-testId job state for the Grok video handler. Mirrors OpenRouterVideoJobMap\n * (openrouter-video.ts): lazy TTL eviction on `get`, FIFO eviction of the\n * oldest entries on `set` when over capacity, delete-before-set TTL refresh,\n * monotonic world-generation counter for reset-mid-flight detection, no\n * background sweep timer. Keys are `${testId}:${requestId}`.\n */\nexport class GrokVideoJobMap {\n private readonly entries = new Map<string, GrokVideoEntry>();\n private worldGeneration = 0;\n\n get generation(): number {\n return this.worldGeneration;\n }\n\n get(key: string): GrokVideoJob | undefined {\n const entry = this.entries.get(key);\n if (!entry) return undefined;\n if (Date.now() - entry.createdAt > GROK_VIDEO_TTL_MS) {\n this.entries.delete(key);\n return undefined;\n }\n return entry.job;\n }\n\n set(key: string, job: GrokVideoJob): void {\n this.entries.delete(key);\n this.entries.set(key, { job, createdAt: Date.now() });\n if (this.entries.size > GROK_VIDEO_MAX_ENTRIES) {\n const excess = this.entries.size - GROK_VIDEO_MAX_ENTRIES;\n const iter = this.entries.keys();\n for (let i = 0; i < excess; i++) {\n const next = iter.next();\n if (!next.done) this.entries.delete(next.value);\n }\n }\n }\n\n delete(key: string): boolean {\n return this.entries.delete(key);\n }\n\n clear(): void {\n this.entries.clear();\n this.worldGeneration++;\n }\n\n get size(): number {\n return this.entries.size;\n }\n}\n\n// ─── Job progression ────────────────────────────────────────────────────────\n\n/**\n * Maps the fixture's terminal video status onto the job lifecycle. Anything\n * that is not \"failed\" — including a fixture authored as \"processing\" — is\n * treated as completed, since this surface always drives jobs to a terminal\n * state.\n */\nfunction terminalStatus(job: GrokVideoReplayJob): GrokVideoStatus {\n return job.video.status === \"failed\" ? \"failed\" : \"completed\";\n}\n\n/**\n * Mutates a job in place to advance its state on a status poll.\n * `pending → in_progress → completed | failed` based on poll-count thresholds.\n * No-op once terminal. The in_progress threshold is checked first so a job\n * whose thresholds are equal still spends one poll in in_progress instead of\n * jumping straight to the terminal status (fal advanceJob semantics).\n */\nfunction advanceJob(job: GrokVideoReplayJob): void {\n if (job.status === \"completed\" || job.status === \"failed\") return;\n\n job.pollCount += 1;\n if (job.status === \"pending\" && job.pollCount >= job.pollsBeforeInProgress) {\n job.status = \"in_progress\";\n } else if (job.pollCount >= job.pollsBeforeCompleted) {\n job.status = terminalStatus(job);\n }\n}\n\n/**\n * Synthesize the Grok wire `progress` (0..100) from poll-count progress toward\n * the completed threshold. A terminal-completed job is always 100 (the only way\n * to reach 100 — it is the wire signal for \"done\"). Every other state reports\n * its progress-at-failure/at-poll, capped at 99. A seed-terminal job with a\n * non-positive completion target has done zero polls toward completion, so it\n * reports 0 (a failed seed job is 0%, never 100).\n */\nfunction grokProgress(job: GrokVideoReplayJob): number {\n if (job.status === \"completed\") return 100;\n const target = job.pollsBeforeCompleted;\n if (target <= 0) return 0;\n const ratio = Math.min(1, job.pollCount / target);\n return Math.min(99, Math.round(ratio * 100));\n}\n\n/** Map the internal job status onto the Grok wire `status` token. */\nfunction grokWireStatus(status: GrokVideoStatus): \"done\" | \"failed\" | \"pending\" {\n if (status === \"completed\") return \"done\";\n if (status === \"failed\") return \"failed\";\n return \"pending\";\n}\n\n/**\n * Synthesizes a structurally valid journal body for field-validation 400s.\n * Mirrors openrouter-video's validationJournalBody: `model` stays a string and\n * `messages` is an empty array; underscore-prefixed keys are stripped so a\n * request cannot spoof handler-set discriminators.\n */\nfunction validationJournalBody(videoReq: GrokVideoRequest): ChatCompletionRequest {\n const rawModel = videoReq.model;\n const model =\n typeof rawModel === \"string\"\n ? rawModel\n : rawModel === undefined\n ? \"\"\n : JSON.stringify(rawModel);\n const sanitized = Object.fromEntries(\n Object.entries(videoReq).filter(([key]) => !key.startsWith(\"_\")),\n );\n return { ...sanitized, model, messages: [] };\n}\n\n// ─── POST /v1/videos/generations — submit ────────────────────────────────────\n\nexport async function handleGrokVideoCreate(\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 jobs: GrokVideoJobMap,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? \"/v1/videos/generations\";\n const method = req.method ?? \"POST\";\n\n // MULTIPART REJECT FIRST — the real Grok API is JSON-only. Reject BEFORE any\n // body parse so a multipart body never reaches the JSON parser (and never\n // reuses Sora's accept-multipart branch).\n const contentType = Array.isArray(req.headers[\"content-type\"])\n ? req.headers[\"content-type\"][0]\n : req.headers[\"content-type\"];\n if ((contentType ?? \"\").toLowerCase().includes(\"multipart/form-data\")) {\n journal.add({\n method,\n path,\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 code: \"invalid_request\",\n error: \"multipart/form-data is not supported — the Grok video API is JSON-only\",\n }),\n );\n return;\n }\n\n let videoReq: GrokVideoRequest;\n try {\n videoReq = JSON.parse(raw) as GrokVideoRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({ code: \"invalid_json\", error: `Malformed JSON: ${detail}` }),\n );\n return;\n }\n\n // Reject bodies that parsed but are not a JSON object before touching fields.\n if (videoReq === null || typeof videoReq !== \"object\" || Array.isArray(videoReq)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({ code: \"invalid_request\", error: \"Request body must be a JSON object\" }),\n );\n return;\n }\n\n const parsedBody = validationJournalBody(videoReq);\n\n if (typeof videoReq.prompt !== \"string\" || !videoReq.prompt) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: parsedBody,\n response: { status: 400, fixture: null },\n });\n const message =\n videoReq.prompt === undefined\n ? \"Missing required parameter: 'prompt'\"\n : \"Invalid type for parameter: 'prompt' must be a non-empty string\";\n writeErrorResponse(res, 400, JSON.stringify({ code: \"invalid_request\", error: message }));\n return;\n }\n\n if (videoReq.model !== undefined && (typeof videoReq.model !== \"string\" || !videoReq.model)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: parsedBody,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n code: \"invalid_request\",\n error: \"Invalid type for parameter: 'model' must be a non-empty string\",\n }),\n );\n return;\n }\n\n const syntheticReq: ChatCompletionRequest = {\n model: videoReq.model ?? DEFAULT_GROK_VIDEO_MODEL,\n messages: [{ role: \"user\", content: videoReq.prompt }],\n _endpointType: \"video\",\n _videoProvider: \"grok\",\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n const snippet = videoReq.prompt.slice(0, 80);\n defaults.logger.debug(\n `No fixture matched for request (model=${syntheticReq.model}, msg=\"${snippet}\")`,\n );\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture\n ? \"fixture\"\n : resolveStrictMode(defaults.strict, req.headers)\n ? \"internal\"\n : defaults.record?.providers.grok\n ? \"proxy\"\n : \"internal\",\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 strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({ code: \"no_fixture_match\", error: strictMessage }),\n );\n return;\n }\n\n if (defaults.record) {\n const outcome = await proxyGrokVideoSubmit({\n req,\n res,\n raw,\n syntheticReq,\n record: defaults.record,\n journal,\n defaults,\n jobs,\n method,\n path,\n });\n if (outcome === \"handled\") return;\n // outcome === \"no_upstream\" — fall through to 404 (fal convention).\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\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({ code: \"not_found\", error: \"No fixture matched\" }),\n );\n return;\n }\n\n const worldGeneration = jobs.generation;\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n if (res.destroyed || res.writableEnded) return;\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isVideoResponse(response)) {\n if (res.destroyed || res.writableEnded) return;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: { message: \"Fixture response is not a video type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n if (res.destroyed || res.writableEnded) return;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const fixtureStatus: string = response.video.status;\n if (fixtureStatus === \"processing\") {\n defaults.logger.warn(\n `Video fixture has status \"processing\" — treated as completed for /v1/videos/generations jobs`,\n );\n } else if (fixtureStatus !== \"completed\" && fixtureStatus !== \"failed\") {\n defaults.logger.warn(\n `Video fixture has unknown status \"${fixtureStatus}\" — treating as completed for /v1/videos/generations jobs`,\n );\n }\n\n const requestId = crypto.randomUUID();\n const progression = resolveProgression(defaults.grokVideo);\n const job: GrokVideoReplayJob = {\n kind: \"replay\",\n requestId,\n status: \"pending\",\n pollCount: 0,\n pollsBeforeInProgress: progression.pollsBeforeInProgress,\n pollsBeforeCompleted: progression.pollsBeforeCompleted,\n video: { ...response.video },\n };\n if (progression.pollsBeforeCompleted === 0) {\n job.status = terminalStatus(job);\n }\n if (jobs.generation === worldGeneration) {\n jobs.set(`${testId}:${requestId}`, job);\n } else {\n defaults.logger.warn(\n `Grok video submit resolved after a fixtures reset — not inserting job ${requestId} into the new world (its polls will 404)`,\n );\n }\n\n // Embed the testId in the RETURNED request_id (not the stored key) so a\n // multi-tenant client can poll the opaque id without an x-test-id header — the\n // testId travels via getTestId's `?testId=` fallback. Mirrors OpenRouter's\n // polling_url treatment; testIdSuffix is \"\" for the default testId (bare id).\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ request_id: requestId + testIdSuffix(testId, \"?\") }));\n}\n\n// ─── GET /v1/videos/{id} — status poll (Grok-first, Sora fall-through) ───────\n\nexport async function handleGrokVideoStatus(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n id: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n grokJobs: GrokVideoJobMap,\n videoStates: VideoStateMap,\n): Promise<void> {\n const testId = getTestId(req);\n const key = `${testId}:${id}`;\n const job = grokJobs.get(key);\n if (!job) {\n // Grok miss → Sora status, UNCHANGED (byte-for-byte; it sets CORS + rolls\n // chaos itself). Disjoint id namespaces make this unambiguous.\n handleVideoStatus(req, res, id, journal, defaults, setCorsHeaders, videoStates);\n return;\n }\n\n // Grok hit: this handler owns the response from here. Set CORS and roll\n // chaos (mirroring handleOpenRouterVideoStatus — chaos rolls before any work).\n setCorsHeaders(res);\n const path = req.url ?? `/v1/videos/${id}`;\n const method = req.method ?? \"GET\";\n\n if (\n applyChaos(\n res,\n null,\n defaults.chaos,\n req.headers,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: null },\n \"internal\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (job.kind === \"record\") {\n if (resolveStrictMode(defaults.strict, req.headers)) {\n defaults.logger.error(\n `STRICT: video job ${id} is proxied live upstream (record mode) — refusing the upstream poll`,\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n code: \"no_fixture_match\",\n error: `Strict mode: video job ${id} is proxied live upstream (record mode) — nothing reaches an upstream under strict mode`,\n }),\n );\n return;\n }\n await proxyGrokVideoRecordPoll({\n req,\n res,\n job,\n key,\n testId,\n fixtures,\n journal,\n defaults,\n jobs: grokJobs,\n method,\n path,\n });\n return;\n }\n\n // Replay: guard BEFORE advancing or journaling (file convention).\n if (res.destroyed || res.writableEnded) return;\n advanceJob(job);\n grokJobs.set(key, job);\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 200, fixture: null },\n });\n\n const body = serializeGrokReplay(job, defaults);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n\n/**\n * Serialize a replay job into the Grok wire body. STORED `video.status` is the\n * 3-value VideoResponse union; the wire `status` is derived here\n * (completed → \"done\"). cost_in_usd_ticks is reconstructed from the stored USD\n * `video.cost` (round(cost * 1e10)).\n */\nfunction serializeGrokReplay(\n job: GrokVideoReplayJob,\n defaults: HandlerDefaults,\n): Record<string, unknown> {\n const body: Record<string, unknown> = {\n request_id: job.requestId,\n status: grokWireStatus(job.status),\n progress: grokProgress(job),\n };\n if (job.status === \"completed\") {\n body.video = {\n url: job.video.url || GROK_DEFAULT_VIDEO_URL,\n duration: job.video.duration ?? 0,\n };\n body.usage = {\n cost_in_usd_ticks: Math.round((job.video.cost ?? 0) * GROK_USD_TICKS_PER_USD),\n };\n } else if (job.status === \"failed\") {\n if (job.video.error === \"\" && !job.emptyErrorWarned) {\n job.emptyErrorWarned = true;\n defaults.logger.warn(\n `Video fixture for job ${job.requestId} has an empty error message — using the default`,\n );\n }\n body.code = \"generation_failed\";\n body.error = job.video.error || \"Video generation failed\";\n }\n return body;\n}\n\n// ─── Record mode: live interactive proxy (submit) ────────────────────────────\n\nconst GROK_VIDEO_SUBMIT_PATH = \"/v1/videos/generations\";\n\n/**\n * Proxy an unmatched Grok video submit to the configured upstream and answer\n * the client with a mock-rewritten `{ request_id }`. The upstream lifecycle is\n * then driven interactively by the client's own polls. Returns \"no_upstream\"\n * when record mode has no grok provider URL — the caller falls through to 404.\n */\nasync function proxyGrokVideoSubmit(args: {\n req: http.IncomingMessage;\n res: http.ServerResponse;\n raw: string;\n syntheticReq: ChatCompletionRequest;\n record: RecordConfig;\n journal: Journal;\n defaults: HandlerDefaults;\n jobs: GrokVideoJobMap;\n method: string;\n path: string;\n}): Promise<\"handled\" | \"no_upstream\"> {\n const { req, res, raw, syntheticReq, record, journal, defaults, jobs, method, path } = args;\n\n const upstreamBase = record.providers.grok;\n if (!upstreamBase) {\n defaults.logger.warn(`No upstream URL configured for provider \"grok\" — cannot proxy`);\n return \"no_upstream\";\n }\n\n const proxyError = (msg: string): \"handled\" => {\n defaults.logger.error(`Grok video submit proxy failed: ${msg}`);\n if (res.destroyed || res.writableEnded) return \"handled\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 502,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 502,\n JSON.stringify({ code: \"proxy_error\", error: `Proxy to upstream failed: ${msg}` }),\n );\n return \"handled\";\n };\n\n let submitUrl: URL;\n let upstreamOrigin: string;\n try {\n submitUrl = resolveUpstreamUrl(upstreamBase, GROK_VIDEO_SUBMIT_PATH);\n upstreamOrigin = new URL(upstreamBase).origin;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return proxyError(`Invalid upstream URL: ${upstreamBase} (${msg})`);\n }\n\n defaults.logger.warn(\n `NO FIXTURE MATCH — proxying video submit to ${upstreamBase}${GROK_VIDEO_SUBMIT_PATH}`,\n );\n\n const worldGeneration = jobs.generation;\n\n let fetched: { status: number; contentType: string | null; text: string };\n try {\n const headers = prepareEgressHeaders(req, submitUrl, \"grok\", record.providerKeys?.grok);\n if (!headers) return proxyError(\"No configured provider credential\");\n const upstreamRes = await fetch(submitUrl, {\n method: \"POST\",\n headers,\n body: raw,\n signal: upstreamTimeoutSignal(record),\n });\n fetched = {\n status: upstreamRes.status,\n contentType: upstreamRes.headers.get(\"content-type\"),\n text: await readEnvelopeText(upstreamRes, record),\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown proxy error\";\n return proxyError(msg);\n }\n\n if (fetched.status === 401 || fetched.status === 403) {\n defaults.logger.warn(\n `Upstream rejected the video submit (${fetched.status}) — relaying the upstream status`,\n );\n if (res.destroyed || res.writableEnded) return \"handled\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: fetched.status,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n res.writeHead(fetched.status, { \"Content-Type\": fetched.contentType ?? \"application/json\" });\n res.end(fetched.text);\n return \"handled\";\n }\n\n let upstreamRequestId: string;\n {\n if (fetched.status < 200 || fetched.status >= 300) {\n return proxyError(`Submit ${fetched.status}: ${fetched.text.slice(0, 200)}`);\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(fetched.text);\n } catch {\n return proxyError(`Submit returned non-JSON: ${fetched.text.slice(0, 200)}`);\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return proxyError(\"Submit response is not a JSON object\");\n }\n const envelope = parsed as Record<string, unknown>;\n upstreamRequestId = String(envelope.request_id ?? \"\").trim();\n if (!upstreamRequestId) {\n return proxyError(\"Submit response missing request_id\");\n }\n }\n\n // Construct the poll URL on the configured provider origin (Grok submit\n // returns only request_id, no polling_url — so there is nothing off-origin\n // to validate; we always build it ourselves on the trusted origin).\n void upstreamOrigin;\n const upstreamPollingUrl = resolveUpstreamUrl(\n upstreamBase,\n `/v1/videos/${encodeURIComponent(upstreamRequestId)}`,\n ).toString();\n\n const testId = getTestId(req);\n const matchRequest = defaults.requestTransform\n ? defaults.requestTransform(syntheticReq)\n : syntheticReq;\n const requestId = crypto.randomUUID();\n const job: GrokVideoRecordJob = {\n kind: \"record\",\n requestId,\n status: \"pending\",\n upstreamRequestId,\n upstreamPollingUrl,\n match: buildFixtureMatch(matchRequest, record),\n };\n if (jobs.generation === worldGeneration) {\n jobs.set(`${testId}:${requestId}`, job);\n } else {\n defaults.logger.warn(\n `Grok video submit for upstream request ${upstreamRequestId} completed after a fixtures reset — not inserting the job into the new world (its polls will 404)`,\n );\n }\n\n if (res.destroyed || res.writableEnded) return \"handled\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 200,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n\n // Embed the testId in the RETURNED request_id (see the replay submit) so\n // record-mode multi-tenant polls resolve via `?testId=`; the upstream poll\n // still uses the bare job.requestId / job.upstreamPollingUrl, untouched.\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ request_id: requestId + testIdSuffix(testId, \"?\") }));\n return \"handled\";\n}\n\n// ─── Record mode: live interactive proxy (status poll + eager capture) ───────\n\n/**\n * Proxy a status poll for a record-mode job 1:1 to the upstream and relay the\n * result with the mock requestId substituted. On `done` the rewritten body is\n * relayed IMMEDIATELY and an eager capture runs (synchronously here — there is\n * NO byte download, only the url/duration/cost from the terminal poll body —\n * but structured to mirror openrouter-video's terminal path). `failed`\n * persists a failed fixture synchronously. `expired` (and any unrepresentable\n * status) pass through with a warn and keep proxying. Every post-await map\n * mutation is identity-guarded. Each successful proxied poll refreshes the TTL.\n */\nasync function proxyGrokVideoRecordPoll(args: {\n req: http.IncomingMessage;\n res: http.ServerResponse;\n job: GrokVideoRecordJob;\n key: string;\n testId: string;\n fixtures: Fixture[];\n journal: Journal;\n defaults: HandlerDefaults;\n jobs: GrokVideoJobMap;\n method: string;\n path: string;\n}): Promise<void> {\n const { req, res, job, key, testId, fixtures, journal, defaults, jobs, method, path } = args;\n const logger = defaults.logger;\n\n const journalProxy = (status: number): void => {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: {\n status,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n };\n\n const proxyError = (msg: string): void => {\n logger.error(`Grok video poll proxy failed: ${msg}`);\n if (res.destroyed || res.writableEnded) return;\n journalProxy(502);\n writeErrorResponse(\n res,\n 502,\n JSON.stringify({ code: \"proxy_error\", error: `Proxy to upstream failed: ${msg}` }),\n );\n };\n\n const record = defaults.record;\n if (!record) {\n proxyError(\"record mode is no longer configured for an in-flight record job\");\n return;\n }\n\n let fetched: { status: number; contentType: string | null; text: string };\n try {\n const target = new URL(job.upstreamPollingUrl);\n const headers = prepareEgressHeaders(req, target, \"grok\", record.providerKeys?.grok);\n if (!headers) {\n proxyError(\"No configured provider credential\");\n return;\n }\n const upstreamRes = await fetch(job.upstreamPollingUrl, {\n headers,\n signal: upstreamTimeoutSignal(record),\n });\n fetched = {\n status: upstreamRes.status,\n contentType: upstreamRes.headers.get(\"content-type\"),\n text: await readEnvelopeText(upstreamRes, record),\n };\n } catch (err) {\n proxyError(err instanceof Error ? err.message : \"Unknown proxy error\");\n return;\n }\n\n if (fetched.status === 401 || fetched.status === 403) {\n logger.warn(\n `Upstream rejected the status poll for job ${job.upstreamRequestId} (${fetched.status}) — relaying the upstream status`,\n );\n if (res.destroyed || res.writableEnded) return;\n journalProxy(fetched.status);\n res.writeHead(fetched.status, { \"Content-Type\": fetched.contentType ?? \"application/json\" });\n res.end(fetched.text);\n return;\n }\n\n let upstreamBody: Record<string, unknown>;\n {\n if (fetched.status < 200 || fetched.status >= 300) {\n proxyError(`Status ${fetched.status}: ${fetched.text.slice(0, 200)}`);\n return;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(fetched.text);\n } catch {\n proxyError(`Status returned non-JSON: ${fetched.text.slice(0, 200)}`);\n return;\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n proxyError(\"Status response is not a JSON object\");\n return;\n }\n upstreamBody = parsed as Record<string, unknown>;\n }\n\n // Rewrite the relay so no upstream id escapes (request_id → mock id);\n // status/progress/video/usage pass through verbatim.\n const relayBody: Record<string, unknown> = { ...upstreamBody, request_id: job.requestId };\n\n const relayJson = (body: Record<string, unknown>): void => {\n if (res.destroyed || res.writableEnded) return;\n journalProxy(200);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n };\n\n const upstreamStatus = String(upstreamBody.status ?? \"\");\n\n if (upstreamStatus === \"pending\" || upstreamStatus === \"in_progress\") {\n if (jobs.get(key) === job) {\n if (!job.capturing && job.status !== \"completed\" && job.status !== \"failed\") {\n job.status = upstreamStatus === \"in_progress\" ? \"in_progress\" : \"pending\";\n }\n jobs.set(key, job); // TTL refresh\n }\n relayJson(relayBody);\n return;\n }\n\n if (upstreamStatus === \"done\") {\n if (record.proxyOnly) {\n if (jobs.get(key) === job) {\n job.status = \"completed\";\n jobs.set(key, job); // TTL refresh\n }\n relayJson(relayBody);\n return;\n }\n\n if (job.capturing || jobs.get(key) !== job) {\n if (jobs.get(key) === job) jobs.set(key, job); // TTL refresh\n relayJson(relayBody);\n return;\n }\n\n // Open the capturing window SYNCHRONOUSLY. There is no byte download, so\n // the capture is synchronous, but mirror the structure: latch capturing,\n // set terminal status, relay, persist, mutate to a terminal replay job.\n job.capturing = true;\n job.status = \"completed\";\n jobs.set(key, job);\n\n relayJson(relayBody);\n\n captureGrokVideoRecordFixture({\n job,\n key,\n testId,\n fixtures,\n defaults,\n jobs,\n record,\n upstreamBody,\n });\n return;\n }\n\n if (upstreamStatus === \"failed\") {\n if (record.proxyOnly) {\n if (jobs.get(key) === job) {\n job.status = \"failed\";\n jobs.set(key, job); // TTL refresh\n }\n relayJson(relayBody);\n return;\n }\n\n if (jobs.get(key) !== job) {\n relayJson(relayBody);\n return;\n }\n\n const rawError = upstreamBody.error;\n let error: string | undefined;\n if (typeof rawError === \"string\" && rawError) {\n error = rawError;\n } else if (rawError !== null && typeof rawError === \"object\" && !Array.isArray(rawError)) {\n const message = (rawError as Record<string, unknown>).message;\n if (typeof message === \"string\" && message) error = message;\n }\n if (error === undefined && rawError !== undefined && rawError !== null) {\n logger.warn(\n `Upstream video job ${job.upstreamRequestId} failed with an unusable error value (${JSON.stringify(rawError).slice(0, 100)}) — recording the fixture without an error message (replay serves the default)`,\n );\n }\n const video: VideoResponse[\"video\"] = {\n id: job.upstreamRequestId,\n status: \"failed\",\n ...(error !== undefined ? { error } : {}),\n };\n const persistResult = persistFixture({\n record,\n providerKey: \"grok\",\n testId,\n fixture: { match: job.match, response: { video } },\n fixtures,\n logger,\n });\n if (persistResult.kind === \"failed\" && !res.headersSent) {\n res.setHeader(\"X-AIMock-Record-Error\", sanitizeHeaderValue(persistResult.error));\n }\n jobs.set(key, {\n kind: \"replay\",\n requestId: job.requestId,\n status: \"failed\",\n pollCount: 0,\n pollsBeforeInProgress: 0,\n pollsBeforeCompleted: 0,\n video: { ...video },\n });\n relayJson(relayBody);\n return;\n }\n\n // expired / anything else: terminal upstream states not representable in\n // VideoResponse.status — pass through (id rewritten), record nothing, keep\n // proxying.\n logger.warn(\n `Upstream video job ${job.upstreamRequestId} reported status \"${upstreamStatus}\" — not representable in fixture video.status; passing through without recording`,\n );\n if (jobs.get(key) === job) {\n jobs.set(key, job);\n }\n relayJson(relayBody);\n}\n\n/**\n * Capture a completed Grok record job into a fixture (PERSIST only — NO byte\n * download): builds `video` from the terminal poll body (id = upstream\n * request_id, status = stored \"completed\", url/duration/cost), persists it, and\n * mutates the map entry into a terminal replay job. World-generation guard\n * before persist (a fixtures reset mid-flight invalidates the world).\n */\nfunction captureGrokVideoRecordFixture(args: {\n job: GrokVideoRecordJob;\n key: string;\n testId: string;\n fixtures: Fixture[];\n defaults: HandlerDefaults;\n jobs: GrokVideoJobMap;\n record: RecordConfig;\n upstreamBody: Record<string, unknown>;\n}): void {\n const { job, key, testId, fixtures, defaults, jobs, record, upstreamBody } = args;\n const logger = defaults.logger;\n\n try {\n const upstreamVideo =\n upstreamBody.video !== null &&\n typeof upstreamBody.video === \"object\" &&\n !Array.isArray(upstreamBody.video)\n ? (upstreamBody.video as Record<string, unknown>)\n : undefined;\n const rawUrl = upstreamVideo?.url;\n const url = typeof rawUrl === \"string\" && rawUrl ? rawUrl : GROK_DEFAULT_VIDEO_URL;\n const rawDuration = upstreamVideo?.duration;\n const duration = typeof rawDuration === \"number\" ? rawDuration : undefined;\n\n const usage =\n upstreamBody.usage !== null &&\n typeof upstreamBody.usage === \"object\" &&\n !Array.isArray(upstreamBody.usage)\n ? (upstreamBody.usage as Record<string, unknown>)\n : undefined;\n const rawTicks = usage?.cost_in_usd_ticks;\n const cost = typeof rawTicks === \"number\" ? rawTicks / GROK_USD_TICKS_PER_USD : undefined;\n\n const video: VideoResponse[\"video\"] = {\n id: job.upstreamRequestId,\n status: \"completed\",\n url,\n ...(duration !== undefined ? { duration } : {}),\n ...(cost !== undefined ? { cost } : {}),\n };\n\n // World-generation guard: a fixtures reset clears the job map, so map\n // identity is a valid proxy for \"same world\".\n if (jobs.get(key) !== job) {\n logger.warn(\n `Grok video capture for job ${job.upstreamRequestId} discarded: the job map no longer holds this job (fixtures reset or TTL eviction) — nothing persisted`,\n );\n return;\n }\n persistFixture({\n record,\n providerKey: \"grok\",\n testId,\n fixture: { match: job.match, response: { video } },\n fixtures,\n logger,\n });\n\n if (jobs.get(key) === job) {\n jobs.set(key, {\n kind: \"replay\",\n requestId: job.requestId,\n status: \"completed\",\n pollCount: 0,\n pollsBeforeInProgress: 0,\n pollsBeforeCompleted: 0,\n video: { ...video },\n });\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.error(\n `Grok video capture for job ${job.upstreamRequestId} failed unexpectedly (${msg}) — fixture not persisted; the job keeps proxying live`,\n );\n } finally {\n // Reset unconditionally (defense-in-depth): the flag lives on this `job`\n // object, so clearing it is always safe — harmless on an orphaned/replaced\n // job (the success path swaps in a fresh replay entry; the old object is\n // discarded either way). The previous `jobs.get(key) === job` guard was a\n // latent landmine: if capture were ever made async/detached, an eviction\n // mid-flight would leave `capturing=true` stuck forever and silently block\n // re-capture. Unconditional reset removes that hazard.\n job.capturing = false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AA8DA,MAAM,2BAA2B;AAGjC,MAAM,yBAAyB;AAI/B,MAAM,yBAAyB;AAI/B,MAAa,yBAAyB;AACtC,MAAM,oBAAoB;;;;;;;;AA+D1B,IAAa,kBAAb,MAA6B;CAC3B,AAAiB,0BAAU,IAAI,KAA6B;CAC5D,AAAQ,kBAAkB;CAE1B,IAAI,aAAqB;AACvB,SAAO,KAAK;;CAGd,IAAI,KAAuC;EACzC,MAAM,QAAQ,KAAK,QAAQ,IAAI,IAAI;AACnC,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,KAAK,KAAK,GAAG,MAAM,YAAY,mBAAmB;AACpD,QAAK,QAAQ,OAAO,IAAI;AACxB;;AAEF,SAAO,MAAM;;CAGf,IAAI,KAAa,KAAyB;AACxC,OAAK,QAAQ,OAAO,IAAI;AACxB,OAAK,QAAQ,IAAI,KAAK;GAAE;GAAK,WAAW,KAAK,KAAK;GAAE,CAAC;AACrD,MAAI,KAAK,QAAQ,OAAO,wBAAwB;GAC9C,MAAM,SAAS,KAAK,QAAQ,OAAO;GACnC,MAAM,OAAO,KAAK,QAAQ,MAAM;AAChC,QAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;IAC/B,MAAM,OAAO,KAAK,MAAM;AACxB,QAAI,CAAC,KAAK,KAAM,MAAK,QAAQ,OAAO,KAAK,MAAM;;;;CAKrD,OAAO,KAAsB;AAC3B,SAAO,KAAK,QAAQ,OAAO,IAAI;;CAGjC,QAAc;AACZ,OAAK,QAAQ,OAAO;AACpB,OAAK;;CAGP,IAAI,OAAe;AACjB,SAAO,KAAK,QAAQ;;;;;;;;;AAYxB,SAAS,eAAe,KAA0C;AAChE,QAAO,IAAI,MAAM,WAAW,WAAW,WAAW;;;;;;;;;AAUpD,SAAS,WAAW,KAA+B;AACjD,KAAI,IAAI,WAAW,eAAe,IAAI,WAAW,SAAU;AAE3D,KAAI,aAAa;AACjB,KAAI,IAAI,WAAW,aAAa,IAAI,aAAa,IAAI,sBACnD,KAAI,SAAS;UACJ,IAAI,aAAa,IAAI,qBAC9B,KAAI,SAAS,eAAe,IAAI;;;;;;;;;;AAYpC,SAAS,aAAa,KAAiC;AACrD,KAAI,IAAI,WAAW,YAAa,QAAO;CACvC,MAAM,SAAS,IAAI;AACnB,KAAI,UAAU,EAAG,QAAO;CACxB,MAAM,QAAQ,KAAK,IAAI,GAAG,IAAI,YAAY,OAAO;AACjD,QAAO,KAAK,IAAI,IAAI,KAAK,MAAM,QAAQ,IAAI,CAAC;;;AAI9C,SAAS,eAAe,QAAwD;AAC9E,KAAI,WAAW,YAAa,QAAO;AACnC,KAAI,WAAW,SAAU,QAAO;AAChC,QAAO;;;;;;;;AAST,SAAS,sBAAsB,UAAmD;CAChF,MAAM,WAAW,SAAS;CAC1B,MAAM,QACJ,OAAO,aAAa,WAChB,WACA,aAAa,SACX,KACA,KAAK,UAAU,SAAS;AAIhC,QAAO;EAAE,GAHS,OAAO,YACvB,OAAO,QAAQ,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,WAAW,IAAI,CAAC,CACjE;EACsB;EAAO,UAAU,EAAE;EAAE;;AAK9C,eAAsB,sBACpB,KACA,KACA,KACA,UACA,SACA,UACA,gBACA,MACe;AACf,gBAAe,IAAI;CACnB,MAAM,OAAO,IAAI,OAAO;CACxB,MAAM,SAAS,IAAI,UAAU;AAQ7B,OAHoB,MAAM,QAAQ,IAAI,QAAQ,gBAAgB,GAC1D,IAAI,QAAQ,gBAAgB,KAC5B,IAAI,QAAQ,oBACI,IAAI,aAAa,CAAC,SAAS,sBAAsB,EAAE;AACrE,UAAQ,IAAI;GACV;GACA;GACA,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU;GACb,MAAM;GACN,OAAO;GACR,CAAC,CACH;AACD;;CAGF,IAAI;AACJ,KAAI;AACF,aAAW,KAAK,MAAM,IAAI;UACnB,UAAU;EACjB,MAAM,SAAS,oBAAoB,QAAQ,SAAS,UAAU;AAC9D,UAAQ,IAAI;GACV;GACA;GACA,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU;GAAE,MAAM;GAAgB,OAAO,mBAAmB;GAAU,CAAC,CAC7E;AACD;;AAIF,KAAI,aAAa,QAAQ,OAAO,aAAa,YAAY,MAAM,QAAQ,SAAS,EAAE;AAChF,UAAQ,IAAI;GACV;GACA;GACA,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU;GAAE,MAAM;GAAmB,OAAO;GAAsC,CAAC,CACzF;AACD;;CAGF,MAAM,aAAa,sBAAsB,SAAS;AAElD,KAAI,OAAO,SAAS,WAAW,YAAY,CAAC,SAAS,QAAQ;AAC3D,UAAQ,IAAI;GACV;GACA;GACA,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;EACF,MAAM,UACJ,SAAS,WAAW,SAChB,yCACA;AACN,wCAAmB,KAAK,KAAK,KAAK,UAAU;GAAE,MAAM;GAAmB,OAAO;GAAS,CAAC,CAAC;AACzF;;AAGF,KAAI,SAAS,UAAU,WAAc,OAAO,SAAS,UAAU,YAAY,CAAC,SAAS,QAAQ;AAC3F,UAAQ,IAAI;GACV;GACA;GACA,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU;GACb,MAAM;GACN,OAAO;GACR,CAAC,CACH;AACD;;CAGF,MAAM,eAAsC;EAC1C,OAAO,SAAS,SAAS;EACzB,UAAU,CAAC;GAAE,MAAM;GAAQ,SAAS,SAAS;GAAQ,CAAC;EACtD,eAAe;EACf,gBAAgB;EAChB,UAAUC,2BAAW,IAAI;EAC1B;CAED,MAAM,SAASC,0BAAU,IAAI;CAC7B,MAAM,EAAE,SAAS,4BAA4BC,sCAC3C,UACA,cACA,QAAQ,6BAA6B,OAAO,EAC5C,SAAS,iBACV;AAED,KAAI,SAAS;AACX,UAAQ,2BAA2B,SAAS,UAAU,OAAO;AAC7D,WAAS,OAAO,MAAM,oBAAoB,KAAK,UAAU,QAAQ,MAAM,CAAC,MAAM,GAAG,IAAI,GAAG;QACnF;EACL,MAAM,UAAU,SAAS,OAAO,MAAM,GAAG,GAAG;AAC5C,WAAS,OAAO,MACd,yCAAyC,aAAa,MAAM,SAAS,QAAQ,IAC9E;;AAGH,KACEC,yBACE,KACA,SACA,SAAS,OACT,IAAI,SACJ,SACA;EAAE;EAAQ;EAAM,SAASJ,+BAAe,IAAI,QAAQ;EAAE,MAAM;EAAc,EAC1E,UACI,YACAK,kCAAkB,SAAS,QAAQ,IAAI,QAAQ,GAC7C,aACA,SAAS,QAAQ,UAAU,OACzB,UACA,YACR,SAAS,UACT,SAAS,OACV,CAED;AAEF,KAAI,CAAC,SAAS;AAEZ,MADwBA,kCAAkB,SAAS,QAAQ,IAAI,QAAQ,EAClD;GACnB,MAAM,gBAAgBC,qCAAqB,wBAAwB;AACnE,YAAS,OAAO,MAAMC,qCAAqB,QAAQ,MAAM,wBAAwB,CAAC;AAClF,WAAQ,IAAI;IACV;IACA;IACA,SAASP,+BAAe,IAAI,QAAQ;IACpC,MAAM;IACN,UAAU;KACR,QAAQ;KACR,SAAS;KACT,GAAGQ,oCAAoB,SAAS,QAAQ,IAAI,QAAQ;KACrD;IACF,CAAC;AACF,yCACE,KACA,KACA,KAAK,UAAU;IAAE,MAAM;IAAoB,OAAO;IAAe,CAAC,CACnE;AACD;;AAGF,MAAI,SAAS,QAaX;OAZgB,MAAM,qBAAqB;IACzC;IACA;IACA;IACA;IACA,QAAQ,SAAS;IACjB;IACA;IACA;IACA;IACA;IACD,CAAC,KACc,UAAW;;AAI7B,UAAQ,IAAI;GACV;GACA;GACA,SAASR,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IACR,QAAQ;IACR,SAAS;IACT,GAAGQ,oCAAoB,SAAS,QAAQ,IAAI,QAAQ;IACrD;GACF,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU;GAAE,MAAM;GAAa,OAAO;GAAsB,CAAC,CACnE;AACD;;CAGF,MAAM,kBAAkB,KAAK;CAC7B,MAAM,WAAW,MAAMC,gCAAgB,SAAS,aAAa;AAE7D,KAAIC,gCAAgB,SAAS,EAAE;AAC7B,MAAI,IAAI,aAAa,IAAI,cAAe;EACxC,MAAM,SAAS,SAAS,UAAU;AAClC,UAAQ,IAAI;GACV;GACA;GACA,SAASV,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE;IAAQ;IAAS;GAC9B,CAAC;AACF,wCAAmB,KAAK,QAAQW,uCAAuB,SAAS,EAAE,EAChE,YAAY,SAAS,YACtB,CAAC;AACF;;AAGF,KAAI,CAACC,gCAAgB,SAAS,EAAE;AAC9B,MAAI,IAAI,aAAa,IAAI,cAAe;AACxC,UAAQ,IAAI;GACV;GACA;GACA,SAASZ,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IAAE,QAAQ;IAAK;IAAS;GACnC,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU,EACb,OAAO;GAAE,SAAS;GAAwC,MAAM;GAAgB,EACjF,CAAC,CACH;AACD;;AAGF,KAAI,IAAI,aAAa,IAAI,cAAe;AACxC,SAAQ,IAAI;EACV;EACA;EACA,SAASA,+BAAe,IAAI,QAAQ;EACpC,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK;GAAS;EACnC,CAAC;CAEF,MAAM,gBAAwB,SAAS,MAAM;AAC7C,KAAI,kBAAkB,aACpB,UAAS,OAAO,KACd,+FACD;UACQ,kBAAkB,eAAe,kBAAkB,SAC5D,UAAS,OAAO,KACd,qCAAqC,cAAc,2DACpD;CAGH,MAAM,YAAYa,oBAAO,YAAY;CACrC,MAAM,cAAcC,+BAAmB,SAAS,UAAU;CAC1D,MAAM,MAA0B;EAC9B,MAAM;EACN;EACA,QAAQ;EACR,WAAW;EACX,uBAAuB,YAAY;EACnC,sBAAsB,YAAY;EAClC,OAAO,EAAE,GAAG,SAAS,OAAO;EAC7B;AACD,KAAI,YAAY,yBAAyB,EACvC,KAAI,SAAS,eAAe,IAAI;AAElC,KAAI,KAAK,eAAe,gBACtB,MAAK,IAAI,GAAG,OAAO,GAAG,aAAa,IAAI;KAEvC,UAAS,OAAO,KACd,yEAAyE,UAAU,0CACpF;AAOH,KAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,KAAI,IAAI,KAAK,UAAU,EAAE,YAAY,YAAYC,wCAAa,QAAQ,IAAI,EAAE,CAAC,CAAC;;AAKhF,eAAsB,sBACpB,KACA,KACA,IACA,UACA,SACA,UACA,gBACA,UACA,aACe;CACf,MAAM,SAASb,0BAAU,IAAI;CAC7B,MAAM,MAAM,GAAG,OAAO,GAAG;CACzB,MAAM,MAAM,SAAS,IAAI,IAAI;AAC7B,KAAI,CAAC,KAAK;AAGR,kCAAkB,KAAK,KAAK,IAAI,SAAS,UAAU,gBAAgB,YAAY;AAC/E;;AAKF,gBAAe,IAAI;CACnB,MAAM,OAAO,IAAI,OAAO,cAAc;CACtC,MAAM,SAAS,IAAI,UAAU;AAE7B,KACEE,yBACE,KACA,MACA,SAAS,OACT,IAAI,SACJ,SACA;EAAE;EAAQ;EAAM,SAASJ,+BAAe,IAAI,QAAQ;EAAE,MAAM;EAAM,EAClE,YACA,SAAS,UACT,SAAS,OACV,CAED;AAEF,KAAI,IAAI,SAAS,UAAU;AACzB,MAAIK,kCAAkB,SAAS,QAAQ,IAAI,QAAQ,EAAE;AACnD,YAAS,OAAO,MACd,qBAAqB,GAAG,sEACzB;AACD,WAAQ,IAAI;IACV;IACA;IACA,SAASL,+BAAe,IAAI,QAAQ;IACpC,MAAM;IACN,UAAU;KACR,QAAQ;KACR,SAAS;KACT,GAAGQ,oCAAoB,SAAS,QAAQ,IAAI,QAAQ;KACrD;IACF,CAAC;AACF,yCACE,KACA,KACA,KAAK,UAAU;IACb,MAAM;IACN,OAAO,0BAA0B,GAAG;IACrC,CAAC,CACH;AACD;;AAEF,QAAM,yBAAyB;GAC7B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,MAAM;GACN;GACA;GACD,CAAC;AACF;;AAIF,KAAI,IAAI,aAAa,IAAI,cAAe;AACxC,YAAW,IAAI;AACf,UAAS,IAAI,KAAK,IAAI;AACtB,SAAQ,IAAI;EACV;EACA;EACA,SAASR,+BAAe,IAAI,QAAQ;EACpC,MAAM;EACN,UAAU;GAAE,QAAQ;GAAK,SAAS;GAAM;EACzC,CAAC;CAEF,MAAM,OAAO,oBAAoB,KAAK,SAAS;AAC/C,KAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,KAAI,IAAI,KAAK,UAAU,KAAK,CAAC;;;;;;;;AAS/B,SAAS,oBACP,KACA,UACyB;CACzB,MAAM,OAAgC;EACpC,YAAY,IAAI;EAChB,QAAQ,eAAe,IAAI,OAAO;EAClC,UAAU,aAAa,IAAI;EAC5B;AACD,KAAI,IAAI,WAAW,aAAa;AAC9B,OAAK,QAAQ;GACX,KAAK,IAAI,MAAM,OAAO;GACtB,UAAU,IAAI,MAAM,YAAY;GACjC;AACD,OAAK,QAAQ,EACX,mBAAmB,KAAK,OAAO,IAAI,MAAM,QAAQ,KAAK,uBAAuB,EAC9E;YACQ,IAAI,WAAW,UAAU;AAClC,MAAI,IAAI,MAAM,UAAU,MAAM,CAAC,IAAI,kBAAkB;AACnD,OAAI,mBAAmB;AACvB,YAAS,OAAO,KACd,yBAAyB,IAAI,UAAU,iDACxC;;AAEH,OAAK,OAAO;AACZ,OAAK,QAAQ,IAAI,MAAM,SAAS;;AAElC,QAAO;;AAKT,MAAM,yBAAyB;;;;;;;AAQ/B,eAAe,qBAAqB,MAWG;CACrC,MAAM,EAAE,KAAK,KAAK,KAAK,cAAc,QAAQ,SAAS,UAAU,MAAM,QAAQ,SAAS;CAEvF,MAAM,eAAe,OAAO,UAAU;AACtC,KAAI,CAAC,cAAc;AACjB,WAAS,OAAO,KAAK,gEAAgE;AACrF,SAAO;;CAGT,MAAM,cAAc,QAA2B;AAC7C,WAAS,OAAO,MAAM,mCAAmC,MAAM;AAC/D,MAAI,IAAI,aAAa,IAAI,cAAe,QAAO;AAC/C,UAAQ,IAAI;GACV;GACA;GACA,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IACR,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,GAAGQ,oCAAoB,SAAS,QAAQ,IAAI,QAAQ;IACrD;GACF,CAAC;AACF,wCACE,KACA,KACA,KAAK,UAAU;GAAE,MAAM;GAAe,OAAO,6BAA6B;GAAO,CAAC,CACnF;AACD,SAAO;;CAGT,IAAI;AAEJ,KAAI;AACF,cAAYQ,+BAAmB,cAAc,uBAAuB;AACpE,EAAiB,IAAI,IAAI,aAAa,CAAC;UAChC,KAAK;AAEZ,SAAO,WAAW,yBAAyB,aAAa,IAD5C,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,CACI,GAAG;;AAGrE,UAAS,OAAO,KACd,+CAA+C,eAAe,yBAC/D;CAED,MAAM,kBAAkB,KAAK;CAE7B,IAAI;AACJ,KAAI;EACF,MAAM,UAAUC,sCAAqB,KAAK,WAAW,QAAQ,OAAO,cAAc,KAAK;AACvF,MAAI,CAAC,QAAS,QAAO,WAAW,oCAAoC;EACpE,MAAM,cAAc,MAAM,MAAM,WAAW;GACzC,QAAQ;GACR;GACA,MAAM;GACN,QAAQC,iDAAsB,OAAO;GACtC,CAAC;AACF,YAAU;GACR,QAAQ,YAAY;GACpB,aAAa,YAAY,QAAQ,IAAI,eAAe;GACpD,MAAM,MAAMC,4CAAiB,aAAa,OAAO;GAClD;UACM,KAAK;AAEZ,SAAO,WADK,eAAe,QAAQ,IAAI,UAAU,sBAC3B;;AAGxB,KAAI,QAAQ,WAAW,OAAO,QAAQ,WAAW,KAAK;AACpD,WAAS,OAAO,KACd,uCAAuC,QAAQ,OAAO,kCACvD;AACD,MAAI,IAAI,aAAa,IAAI,cAAe,QAAO;AAC/C,UAAQ,IAAI;GACV;GACA;GACA,SAASnB,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IACR,QAAQ,QAAQ;IAChB,SAAS;IACT,QAAQ;IACR,GAAGQ,oCAAoB,SAAS,QAAQ,IAAI,QAAQ;IACrD;GACF,CAAC;AACF,MAAI,UAAU,QAAQ,QAAQ,EAAE,gBAAgB,QAAQ,eAAe,oBAAoB,CAAC;AAC5F,MAAI,IAAI,QAAQ,KAAK;AACrB,SAAO;;CAGT,IAAI;CACJ;AACE,MAAI,QAAQ,SAAS,OAAO,QAAQ,UAAU,IAC5C,QAAO,WAAW,UAAU,QAAQ,OAAO,IAAI,QAAQ,KAAK,MAAM,GAAG,IAAI,GAAG;EAE9E,IAAI;AACJ,MAAI;AACF,YAAS,KAAK,MAAM,QAAQ,KAAK;UAC3B;AACN,UAAO,WAAW,6BAA6B,QAAQ,KAAK,MAAM,GAAG,IAAI,GAAG;;AAE9E,MAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,MAAM,QAAQ,OAAO,CACxE,QAAO,WAAW,uCAAuC;EAE3D,MAAM,WAAW;AACjB,sBAAoB,OAAO,SAAS,cAAc,GAAG,CAAC,MAAM;AAC5D,MAAI,CAAC,kBACH,QAAO,WAAW,qCAAqC;;CAQ3D,MAAM,qBAAqBQ,+BACzB,cACA,cAAc,mBAAmB,kBAAkB,GACpD,CAAC,UAAU;CAEZ,MAAM,SAASd,0BAAU,IAAI;CAC7B,MAAM,eAAe,SAAS,mBAC1B,SAAS,iBAAiB,aAAa,GACvC;CACJ,MAAM,YAAYW,oBAAO,YAAY;CACrC,MAAM,MAA0B;EAC9B,MAAM;EACN;EACA,QAAQ;EACR;EACA;EACA,OAAOO,mCAAkB,cAAc,OAAO;EAC/C;AACD,KAAI,KAAK,eAAe,gBACtB,MAAK,IAAI,GAAG,OAAO,GAAG,aAAa,IAAI;KAEvC,UAAS,OAAO,KACd,0CAA0C,kBAAkB,mGAC7D;AAGH,KAAI,IAAI,aAAa,IAAI,cAAe,QAAO;AAC/C,SAAQ,IAAI;EACV;EACA;EACA,SAASpB,+BAAe,IAAI,QAAQ;EACpC,MAAM;EACN,UAAU;GACR,QAAQ;GACR,SAAS;GACT,QAAQ;GACR,GAAGQ,oCAAoB,SAAS,QAAQ,IAAI,QAAQ;GACrD;EACF,CAAC;AAKF,KAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,KAAI,IAAI,KAAK,UAAU,EAAE,YAAY,YAAYO,wCAAa,QAAQ,IAAI,EAAE,CAAC,CAAC;AAC9E,QAAO;;;;;;;;;;;;AAeT,eAAe,yBAAyB,MAYtB;CAChB,MAAM,EAAE,KAAK,KAAK,KAAK,KAAK,QAAQ,UAAU,SAAS,UAAU,MAAM,QAAQ,SAAS;CACxF,MAAM,SAAS,SAAS;CAExB,MAAM,gBAAgB,WAAyB;AAC7C,UAAQ,IAAI;GACV;GACA;GACA,SAASf,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,UAAU;IACR;IACA,SAAS;IACT,QAAQ;IACR,GAAGQ,oCAAoB,SAAS,QAAQ,IAAI,QAAQ;IACrD;GACF,CAAC;;CAGJ,MAAM,cAAc,QAAsB;AACxC,SAAO,MAAM,iCAAiC,MAAM;AACpD,MAAI,IAAI,aAAa,IAAI,cAAe;AACxC,eAAa,IAAI;AACjB,wCACE,KACA,KACA,KAAK,UAAU;GAAE,MAAM;GAAe,OAAO,6BAA6B;GAAO,CAAC,CACnF;;CAGH,MAAM,SAAS,SAAS;AACxB,KAAI,CAAC,QAAQ;AACX,aAAW,kEAAkE;AAC7E;;CAGF,IAAI;AACJ,KAAI;EAEF,MAAM,UAAUS,sCAAqB,KADtB,IAAI,IAAI,IAAI,mBAAmB,EACI,QAAQ,OAAO,cAAc,KAAK;AACpF,MAAI,CAAC,SAAS;AACZ,cAAW,oCAAoC;AAC/C;;EAEF,MAAM,cAAc,MAAM,MAAM,IAAI,oBAAoB;GACtD;GACA,QAAQC,iDAAsB,OAAO;GACtC,CAAC;AACF,YAAU;GACR,QAAQ,YAAY;GACpB,aAAa,YAAY,QAAQ,IAAI,eAAe;GACpD,MAAM,MAAMC,4CAAiB,aAAa,OAAO;GAClD;UACM,KAAK;AACZ,aAAW,eAAe,QAAQ,IAAI,UAAU,sBAAsB;AACtE;;AAGF,KAAI,QAAQ,WAAW,OAAO,QAAQ,WAAW,KAAK;AACpD,SAAO,KACL,6CAA6C,IAAI,kBAAkB,IAAI,QAAQ,OAAO,kCACvF;AACD,MAAI,IAAI,aAAa,IAAI,cAAe;AACxC,eAAa,QAAQ,OAAO;AAC5B,MAAI,UAAU,QAAQ,QAAQ,EAAE,gBAAgB,QAAQ,eAAe,oBAAoB,CAAC;AAC5F,MAAI,IAAI,QAAQ,KAAK;AACrB;;CAGF,IAAI;CACJ;AACE,MAAI,QAAQ,SAAS,OAAO,QAAQ,UAAU,KAAK;AACjD,cAAW,UAAU,QAAQ,OAAO,IAAI,QAAQ,KAAK,MAAM,GAAG,IAAI,GAAG;AACrE;;EAEF,IAAI;AACJ,MAAI;AACF,YAAS,KAAK,MAAM,QAAQ,KAAK;UAC3B;AACN,cAAW,6BAA6B,QAAQ,KAAK,MAAM,GAAG,IAAI,GAAG;AACrE;;AAEF,MAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,MAAM,QAAQ,OAAO,EAAE;AAC1E,cAAW,uCAAuC;AAClD;;AAEF,iBAAe;;CAKjB,MAAM,YAAqC;EAAE,GAAG;EAAc,YAAY,IAAI;EAAW;CAEzF,MAAM,aAAa,SAAwC;AACzD,MAAI,IAAI,aAAa,IAAI,cAAe;AACxC,eAAa,IAAI;AACjB,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IAAI,KAAK,UAAU,KAAK,CAAC;;CAG/B,MAAM,iBAAiB,OAAO,aAAa,UAAU,GAAG;AAExD,KAAI,mBAAmB,aAAa,mBAAmB,eAAe;AACpE,MAAI,KAAK,IAAI,IAAI,KAAK,KAAK;AACzB,OAAI,CAAC,IAAI,aAAa,IAAI,WAAW,eAAe,IAAI,WAAW,SACjE,KAAI,SAAS,mBAAmB,gBAAgB,gBAAgB;AAElE,QAAK,IAAI,KAAK,IAAI;;AAEpB,YAAU,UAAU;AACpB;;AAGF,KAAI,mBAAmB,QAAQ;AAC7B,MAAI,OAAO,WAAW;AACpB,OAAI,KAAK,IAAI,IAAI,KAAK,KAAK;AACzB,QAAI,SAAS;AACb,SAAK,IAAI,KAAK,IAAI;;AAEpB,aAAU,UAAU;AACpB;;AAGF,MAAI,IAAI,aAAa,KAAK,IAAI,IAAI,KAAK,KAAK;AAC1C,OAAI,KAAK,IAAI,IAAI,KAAK,IAAK,MAAK,IAAI,KAAK,IAAI;AAC7C,aAAU,UAAU;AACpB;;AAMF,MAAI,YAAY;AAChB,MAAI,SAAS;AACb,OAAK,IAAI,KAAK,IAAI;AAElB,YAAU,UAAU;AAEpB,gCAA8B;GAC5B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD,CAAC;AACF;;AAGF,KAAI,mBAAmB,UAAU;AAC/B,MAAI,OAAO,WAAW;AACpB,OAAI,KAAK,IAAI,IAAI,KAAK,KAAK;AACzB,QAAI,SAAS;AACb,SAAK,IAAI,KAAK,IAAI;;AAEpB,aAAU,UAAU;AACpB;;AAGF,MAAI,KAAK,IAAI,IAAI,KAAK,KAAK;AACzB,aAAU,UAAU;AACpB;;EAGF,MAAM,WAAW,aAAa;EAC9B,IAAI;AACJ,MAAI,OAAO,aAAa,YAAY,SAClC,SAAQ;WACC,aAAa,QAAQ,OAAO,aAAa,YAAY,CAAC,MAAM,QAAQ,SAAS,EAAE;GACxF,MAAM,UAAW,SAAqC;AACtD,OAAI,OAAO,YAAY,YAAY,QAAS,SAAQ;;AAEtD,MAAI,UAAU,UAAa,aAAa,UAAa,aAAa,KAChE,QAAO,KACL,sBAAsB,IAAI,kBAAkB,wCAAwC,KAAK,UAAU,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,gFAC5H;EAEH,MAAM,QAAgC;GACpC,IAAI,IAAI;GACR,QAAQ;GACR,GAAI,UAAU,SAAY,EAAE,OAAO,GAAG,EAAE;GACzC;EACD,MAAM,gBAAgBE,gCAAe;GACnC;GACA,aAAa;GACb;GACA,SAAS;IAAE,OAAO,IAAI;IAAO,UAAU,EAAE,OAAO;IAAE;GAClD;GACA;GACD,CAAC;AACF,MAAI,cAAc,SAAS,YAAY,CAAC,IAAI,YAC1C,KAAI,UAAU,yBAAyBC,qCAAoB,cAAc,MAAM,CAAC;AAElF,OAAK,IAAI,KAAK;GACZ,MAAM;GACN,WAAW,IAAI;GACf,QAAQ;GACR,WAAW;GACX,uBAAuB;GACvB,sBAAsB;GACtB,OAAO,EAAE,GAAG,OAAO;GACpB,CAAC;AACF,YAAU,UAAU;AACpB;;AAMF,QAAO,KACL,sBAAsB,IAAI,kBAAkB,oBAAoB,eAAe,kFAChF;AACD,KAAI,KAAK,IAAI,IAAI,KAAK,IACpB,MAAK,IAAI,KAAK,IAAI;AAEpB,WAAU,UAAU;;;;;;;;;AAUtB,SAAS,8BAA8B,MAS9B;CACP,MAAM,EAAE,KAAK,KAAK,QAAQ,UAAU,UAAU,MAAM,QAAQ,iBAAiB;CAC7E,MAAM,SAAS,SAAS;AAExB,KAAI;EACF,MAAM,gBACJ,aAAa,UAAU,QACvB,OAAO,aAAa,UAAU,YAC9B,CAAC,MAAM,QAAQ,aAAa,MAAM,GAC7B,aAAa,QACd;EACN,MAAM,SAAS,eAAe;EAC9B,MAAM,MAAM,OAAO,WAAW,YAAY,SAAS,SAAS;EAC5D,MAAM,cAAc,eAAe;EACnC,MAAM,WAAW,OAAO,gBAAgB,WAAW,cAAc;EAQjE,MAAM,YALJ,aAAa,UAAU,QACvB,OAAO,aAAa,UAAU,YAC9B,CAAC,MAAM,QAAQ,aAAa,MAAM,GAC7B,aAAa,QACd,SACkB;EACxB,MAAM,OAAO,OAAO,aAAa,WAAW,WAAW,yBAAyB;EAEhF,MAAM,QAAgC;GACpC,IAAI,IAAI;GACR,QAAQ;GACR;GACA,GAAI,aAAa,SAAY,EAAE,UAAU,GAAG,EAAE;GAC9C,GAAI,SAAS,SAAY,EAAE,MAAM,GAAG,EAAE;GACvC;AAID,MAAI,KAAK,IAAI,IAAI,KAAK,KAAK;AACzB,UAAO,KACL,8BAA8B,IAAI,kBAAkB,uGACrD;AACD;;AAEF,kCAAe;GACb;GACA,aAAa;GACb;GACA,SAAS;IAAE,OAAO,IAAI;IAAO,UAAU,EAAE,OAAO;IAAE;GAClD;GACA;GACD,CAAC;AAEF,MAAI,KAAK,IAAI,IAAI,KAAK,IACpB,MAAK,IAAI,KAAK;GACZ,MAAM;GACN,WAAW,IAAI;GACf,QAAQ;GACR,WAAW;GACX,uBAAuB;GACvB,sBAAsB;GACtB,OAAO,EAAE,GAAG,OAAO;GACpB,CAAC;UAEG,KAAK;EACZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,SAAO,MACL,8BAA8B,IAAI,kBAAkB,wBAAwB,IAAI,wDACjF;WACO;AAQR,MAAI,YAAY"}
1
+ {"version":3,"file":"grok-video.cjs","names":["flattenHeaders","getContext","getTestId","matchFixtureDiagnostic","applyChaos","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","resolveResponse","isErrorResponse","serializeErrorResponse","isVideoResponse","crypto","resolveProgression","testIdSuffix","resolveUpstreamUrl","prepareEgressHeaders","upstreamTimeoutSignal","readEnvelopeText","buildFixtureMatch","persistFixture","sanitizeHeaderValue"],"sources":["../src/grok-video.ts"],"sourcesContent":["import type * as http from \"node:http\";\nimport crypto from \"node:crypto\";\nimport type {\n ChatCompletionRequest,\n Fixture,\n HandlerDefaults,\n RecordConfig,\n VideoResponse,\n} from \"./types.js\";\nimport {\n isVideoResponse,\n isErrorResponse,\n serializeErrorResponse,\n flattenHeaders,\n getTestId,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n getContext,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { resolveProgression } from \"./fal.js\";\nimport {\n buildFixtureMatch,\n prepareEgressHeaders,\n persistFixture,\n sanitizeHeaderValue,\n} from \"./recorder.js\";\nimport { resolveUpstreamUrl } from \"./url.js\";\nimport { handleVideoStatus, type VideoStateMap } from \"./video.js\";\nimport { readEnvelopeText, testIdSuffix, upstreamTimeoutSignal } from \"./video-proxy-shared.js\";\n\n/**\n * xAI Grok Imagine async video lifecycle mock. Submit\n * `POST /v1/videos/generations` returns `{ request_id }`; status\n * `GET /v1/videos/{request_id}` polls `pending → done | failed | expired` with\n * a synthesized `progress`. Multipart submits are rejected with HTTP 400\n * BEFORE body parse (the real API is JSON-only). `video.url` is served AS-IS —\n * aimock does NOT proxy or capture video bytes. With `record.providers.grok`\n * configured an unmatched submit becomes a live interactive proxy (submit +\n * poll forwarded 1:1, eager fixture capture of url/duration/cost on terminal\n * status). Strict mode wins.\n *\n * Grok status shares the `/v1/videos/{id}` path with Sora: the dispatch does a\n * job-map-first lookup and falls through to the UNCHANGED Sora\n * `handleVideoStatus` on a miss (disjoint id namespaces → unambiguous).\n *\n * cost_in_usd_ticks ↔ USD: USD = ticks / 1e10 (ASSUMED). Record persists USD in\n * `video.cost`; replay reconstructs ticks = round(cost * 1e10).\n */\n\ninterface GrokVideoRequest {\n model?: string;\n prompt?: string;\n [key: string]: unknown;\n}\n\nconst DEFAULT_GROK_VIDEO_MODEL = \"grok-imagine-video\";\n\n// USD ←→ Grok cost_in_usd_ticks conversion (ASSUMED: 1e10 ticks per USD).\nconst GROK_USD_TICKS_PER_USD = 1e10;\n\n// Default placeholder when a completed fixture omits `video.url` — every other\n// coercion on this surface is documented; the real API always returns a url.\nconst GROK_DEFAULT_VIDEO_URL = \"https://cdn.x.ai/aimock-placeholder-video.mp4\";\n\n// ─── GrokVideoJobMap (TTL + bounded) ────────────────────────────────────────\n\nexport const GROK_VIDEO_MAX_ENTRIES = 10_000;\nconst GROK_VIDEO_TTL_MS = 3_600_000; // 1 hour\n\ntype GrokVideoStatus = \"pending\" | \"in_progress\" | \"completed\" | \"failed\";\n\ninterface GrokVideoReplayJob {\n kind: \"replay\";\n requestId: string;\n status: GrokVideoStatus;\n /** Number of status polls the caller has made against this job. */\n pollCount: number;\n /** Poll-count threshold for `pending → in_progress` transition. */\n pollsBeforeInProgress: number;\n /** Poll-count threshold for the transition to the terminal status. */\n pollsBeforeCompleted: number;\n /** The matched fixture's video object (terminal status, url, duration, cost, error). */\n video: VideoResponse[\"video\"];\n /** Latch for the empty-`error` authoring warn on failed polls (once per job). */\n emptyErrorWarned?: boolean;\n}\n\n/**\n * A job whose lifecycle is proxied live upstream (record mode, no fixture\n * matched at submit). Every client poll is forwarded 1:1 to\n * `upstreamPollingUrl`; when the upstream reports a terminal status (`done`/\n * `failed`) the entry is captured as a fixture and MUTATED into a terminal\n * GrokVideoReplayJob. `expired` (and any unrepresentable status) pass through\n * with a warn and keep proxying. Under `record.proxyOnly` nothing is ever\n * captured or mutated.\n */\ninterface GrokVideoRecordJob {\n kind: \"record\";\n /** The mock-issued requestId the client polls with. */\n requestId: string;\n /** Last upstream status relayed to the client. */\n status: GrokVideoStatus;\n /** Upstream's own request id (recorded as the fixture's video.id). */\n upstreamRequestId: string;\n /** Origin-validated absolute upstream URL proxied on every client poll. */\n upstreamPollingUrl: string;\n /** Match snapshot built at submit time (post requestTransform). */\n match: Fixture[\"match\"];\n /**\n * Set synchronously before the first await of the eager-capture sequence so\n * a concurrent terminal poll relays the upstream body instead of starting a\n * second capture/persist.\n */\n capturing?: boolean;\n}\n\nexport type GrokVideoJob = GrokVideoReplayJob | GrokVideoRecordJob;\n\ninterface GrokVideoEntry {\n job: GrokVideoJob;\n createdAt: number;\n}\n\n/**\n * Per-testId job state for the Grok video handler. Mirrors OpenRouterVideoJobMap\n * (openrouter-video.ts): lazy TTL eviction on `get`, FIFO eviction of the\n * oldest entries on `set` when over capacity, delete-before-set TTL refresh,\n * monotonic world-generation counter for reset-mid-flight detection, no\n * background sweep timer. Keys are `${testId}:${requestId}`.\n */\nexport class GrokVideoJobMap {\n private readonly entries = new Map<string, GrokVideoEntry>();\n private worldGeneration = 0;\n\n get generation(): number {\n return this.worldGeneration;\n }\n\n get(key: string): GrokVideoJob | undefined {\n const entry = this.entries.get(key);\n if (!entry) return undefined;\n if (Date.now() - entry.createdAt > GROK_VIDEO_TTL_MS) {\n this.entries.delete(key);\n return undefined;\n }\n return entry.job;\n }\n\n set(key: string, job: GrokVideoJob): void {\n this.entries.delete(key);\n this.entries.set(key, { job, createdAt: Date.now() });\n if (this.entries.size > GROK_VIDEO_MAX_ENTRIES) {\n const excess = this.entries.size - GROK_VIDEO_MAX_ENTRIES;\n const iter = this.entries.keys();\n for (let i = 0; i < excess; i++) {\n const next = iter.next();\n if (!next.done) this.entries.delete(next.value);\n }\n }\n }\n\n delete(key: string): boolean {\n return this.entries.delete(key);\n }\n\n clear(): void {\n this.entries.clear();\n this.worldGeneration++;\n }\n\n get size(): number {\n return this.entries.size;\n }\n}\n\n// ─── Job progression ────────────────────────────────────────────────────────\n\n/**\n * Maps the fixture's terminal video status onto the job lifecycle. Anything\n * that is not \"failed\" — including a fixture authored as \"processing\" — is\n * treated as completed, since this surface always drives jobs to a terminal\n * state.\n */\nfunction terminalStatus(job: GrokVideoReplayJob): GrokVideoStatus {\n return job.video.status === \"failed\" ? \"failed\" : \"completed\";\n}\n\n/**\n * Mutates a job in place to advance its state on a status poll.\n * `pending → in_progress → completed | failed` based on poll-count thresholds.\n * No-op once terminal. The in_progress threshold is checked first so a job\n * whose thresholds are equal still spends one poll in in_progress instead of\n * jumping straight to the terminal status (fal advanceJob semantics).\n */\nfunction advanceJob(job: GrokVideoReplayJob): void {\n if (job.status === \"completed\" || job.status === \"failed\") return;\n\n job.pollCount += 1;\n if (job.status === \"pending\" && job.pollCount >= job.pollsBeforeInProgress) {\n job.status = \"in_progress\";\n } else if (job.pollCount >= job.pollsBeforeCompleted) {\n job.status = terminalStatus(job);\n }\n}\n\n/**\n * Synthesize the Grok wire `progress` (0..100) from poll-count progress toward\n * the completed threshold. A terminal-completed job is always 100 (the only way\n * to reach 100 — it is the wire signal for \"done\"). Every other state reports\n * its progress-at-failure/at-poll, capped at 99. A seed-terminal job with a\n * non-positive completion target has done zero polls toward completion, so it\n * reports 0 (a failed seed job is 0%, never 100).\n */\nfunction grokProgress(job: GrokVideoReplayJob): number {\n if (job.status === \"completed\") return 100;\n const target = job.pollsBeforeCompleted;\n if (target <= 0) return 0;\n const ratio = Math.min(1, job.pollCount / target);\n return Math.min(99, Math.round(ratio * 100));\n}\n\n/** Map the internal job status onto the Grok wire `status` token. */\nfunction grokWireStatus(status: GrokVideoStatus): \"done\" | \"failed\" | \"pending\" {\n if (status === \"completed\") return \"done\";\n if (status === \"failed\") return \"failed\";\n return \"pending\";\n}\n\n/**\n * Synthesizes a structurally valid journal body for field-validation 400s.\n * Mirrors openrouter-video's validationJournalBody: `model` stays a string and\n * `messages` is an empty array; underscore-prefixed keys are stripped so a\n * request cannot spoof handler-set discriminators.\n */\nfunction validationJournalBody(videoReq: GrokVideoRequest): ChatCompletionRequest {\n const rawModel = videoReq.model;\n const model =\n typeof rawModel === \"string\"\n ? rawModel\n : rawModel === undefined\n ? \"\"\n : JSON.stringify(rawModel);\n const sanitized = Object.fromEntries(\n Object.entries(videoReq).filter(([key]) => !key.startsWith(\"_\")),\n );\n return { ...sanitized, model, messages: [] };\n}\n\n// ─── POST /v1/videos/generations — submit ────────────────────────────────────\n\nexport async function handleGrokVideoCreate(\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 jobs: GrokVideoJobMap,\n): Promise<void> {\n setCorsHeaders(res);\n const path = req.url ?? \"/v1/videos/generations\";\n const method = req.method ?? \"POST\";\n\n // MULTIPART REJECT FIRST — the real Grok API is JSON-only. Reject BEFORE any\n // body parse so a multipart body never reaches the JSON parser (and never\n // reuses Sora's accept-multipart branch).\n const contentType = Array.isArray(req.headers[\"content-type\"])\n ? req.headers[\"content-type\"][0]\n : req.headers[\"content-type\"];\n if ((contentType ?? \"\").toLowerCase().includes(\"multipart/form-data\")) {\n journal.add({\n method,\n path,\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 code: \"invalid_request\",\n error: \"multipart/form-data is not supported — the Grok video API is JSON-only\",\n }),\n );\n return;\n }\n\n let videoReq: GrokVideoRequest;\n try {\n videoReq = JSON.parse(raw) as GrokVideoRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({ code: \"invalid_json\", error: `Malformed JSON: ${detail}` }),\n );\n return;\n }\n\n // Reject bodies that parsed but are not a JSON object before touching fields.\n if (videoReq === null || typeof videoReq !== \"object\" || Array.isArray(videoReq)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({ code: \"invalid_request\", error: \"Request body must be a JSON object\" }),\n );\n return;\n }\n\n const parsedBody = validationJournalBody(videoReq);\n\n if (typeof videoReq.prompt !== \"string\" || !videoReq.prompt) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: parsedBody,\n response: { status: 400, fixture: null },\n });\n const message =\n videoReq.prompt === undefined\n ? \"Missing required parameter: 'prompt'\"\n : \"Invalid type for parameter: 'prompt' must be a non-empty string\";\n writeErrorResponse(res, 400, JSON.stringify({ code: \"invalid_request\", error: message }));\n return;\n }\n\n if (videoReq.model !== undefined && (typeof videoReq.model !== \"string\" || !videoReq.model)) {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: parsedBody,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify({\n code: \"invalid_request\",\n error: \"Invalid type for parameter: 'model' must be a non-empty string\",\n }),\n );\n return;\n }\n\n const syntheticReq: ChatCompletionRequest = {\n model: videoReq.model ?? DEFAULT_GROK_VIDEO_MODEL,\n messages: [{ role: \"user\", content: videoReq.prompt }],\n _endpointType: \"video\",\n _videoProvider: \"grok\",\n _context: getContext(req),\n };\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n syntheticReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n } else {\n const snippet = videoReq.prompt.slice(0, 80);\n defaults.logger.debug(\n `No fixture matched for request (model=${syntheticReq.model}, msg=\"${snippet}\")`,\n );\n }\n\n if (\n applyChaos(\n res,\n fixture,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: syntheticReq },\n fixture\n ? \"fixture\"\n : resolveStrictMode(defaults.strict, req.headers)\n ? \"internal\"\n : defaults.record?.providers.grok\n ? \"proxy\"\n : \"internal\",\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 strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n defaults.logger.error(strictNoMatchLogLine(method, path, skippedBySequenceOrTurn));\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({ code: \"no_fixture_match\", error: strictMessage }),\n );\n return;\n }\n\n if (defaults.record) {\n const outcome = await proxyGrokVideoSubmit({\n req,\n res,\n raw,\n syntheticReq,\n record: defaults.record,\n journal,\n defaults,\n jobs,\n method,\n path,\n });\n if (outcome === \"handled\") return;\n // outcome === \"no_upstream\" — fall through to 404 (fal convention).\n }\n\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\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({ code: \"not_found\", error: \"No fixture matched\" }),\n );\n return;\n }\n\n const worldGeneration = jobs.generation;\n const response = await resolveResponse(fixture, syntheticReq);\n\n if (isErrorResponse(response)) {\n if (res.destroyed || res.writableEnded) return;\n const status = response.status ?? 500;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status, fixture },\n });\n writeErrorResponse(res, status, serializeErrorResponse(response), {\n retryAfter: response.retryAfter,\n });\n return;\n }\n\n if (!isVideoResponse(response)) {\n if (res.destroyed || res.writableEnded) return;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify({\n error: { message: \"Fixture response is not a video type\", type: \"server_error\" },\n }),\n );\n return;\n }\n\n if (res.destroyed || res.writableEnded) return;\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: { status: 200, fixture },\n });\n\n const fixtureStatus: string = response.video.status;\n if (fixtureStatus === \"processing\") {\n defaults.logger.warn(\n `Video fixture has status \"processing\" — treated as completed for /v1/videos/generations jobs`,\n );\n } else if (fixtureStatus !== \"completed\" && fixtureStatus !== \"failed\") {\n defaults.logger.warn(\n `Video fixture has unknown status \"${fixtureStatus}\" — treating as completed for /v1/videos/generations jobs`,\n );\n }\n\n const requestId = crypto.randomUUID();\n const progression = resolveProgression(defaults.grokVideo);\n const job: GrokVideoReplayJob = {\n kind: \"replay\",\n requestId,\n status: \"pending\",\n pollCount: 0,\n pollsBeforeInProgress: progression.pollsBeforeInProgress,\n pollsBeforeCompleted: progression.pollsBeforeCompleted,\n video: { ...response.video },\n };\n if (progression.pollsBeforeCompleted === 0) {\n job.status = terminalStatus(job);\n }\n if (jobs.generation === worldGeneration) {\n jobs.set(`${testId}:${requestId}`, job);\n } else {\n defaults.logger.warn(\n `Grok video submit resolved after a fixtures reset — not inserting job ${requestId} into the new world (its polls will 404)`,\n );\n }\n\n // Embed the testId in the RETURNED request_id (not the stored key) so a\n // multi-tenant client can poll the opaque id without an x-test-id header — the\n // testId travels via getTestId's `?testId=` fallback. Mirrors OpenRouter's\n // polling_url treatment; testIdSuffix is \"\" for the default testId (bare id).\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ request_id: requestId + testIdSuffix(testId, \"?\") }));\n}\n\n// ─── GET /v1/videos/{id} — status poll (Grok-first, Sora fall-through) ───────\n\nexport async function handleGrokVideoStatus(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n id: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n grokJobs: GrokVideoJobMap,\n videoStates: VideoStateMap,\n): Promise<void> {\n const testId = getTestId(req);\n const key = `${testId}:${id}`;\n const job = grokJobs.get(key);\n if (!job) {\n // Grok miss → Sora status, UNCHANGED (byte-for-byte; it sets CORS + rolls\n // chaos itself). Disjoint id namespaces make this unambiguous.\n handleVideoStatus(req, res, id, journal, defaults, setCorsHeaders, videoStates);\n return;\n }\n\n // Grok hit: this handler owns the response from here. Set CORS and roll\n // chaos (mirroring handleOpenRouterVideoStatus — chaos rolls before any work).\n setCorsHeaders(res);\n const path = req.url ?? `/v1/videos/${id}`;\n const method = req.method ?? \"GET\";\n\n if (\n applyChaos(\n res,\n null,\n defaults.chaos,\n req.headers,\n req.url,\n journal,\n { method, path, headers: flattenHeaders(req.headers), body: null },\n \"internal\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (job.kind === \"record\") {\n if (resolveStrictMode(defaults.strict, req.headers)) {\n defaults.logger.error(\n `STRICT: video job ${id} is proxied live upstream (record mode) — refusing the upstream poll`,\n );\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 503,\n JSON.stringify({\n code: \"no_fixture_match\",\n error: `Strict mode: video job ${id} is proxied live upstream (record mode) — nothing reaches an upstream under strict mode`,\n }),\n );\n return;\n }\n await proxyGrokVideoRecordPoll({\n req,\n res,\n job,\n key,\n testId,\n fixtures,\n journal,\n defaults,\n jobs: grokJobs,\n method,\n path,\n });\n return;\n }\n\n // Replay: guard BEFORE advancing or journaling (file convention).\n if (res.destroyed || res.writableEnded) return;\n advanceJob(job);\n grokJobs.set(key, job);\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 200, fixture: null },\n });\n\n const body = serializeGrokReplay(job, defaults);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n}\n\n/**\n * Serialize a replay job into the Grok wire body. STORED `video.status` is the\n * 3-value VideoResponse union; the wire `status` is derived here\n * (completed → \"done\"). cost_in_usd_ticks is reconstructed from the stored USD\n * `video.cost` (round(cost * 1e10)).\n */\nfunction serializeGrokReplay(\n job: GrokVideoReplayJob,\n defaults: HandlerDefaults,\n): Record<string, unknown> {\n const body: Record<string, unknown> = {\n request_id: job.requestId,\n status: grokWireStatus(job.status),\n progress: grokProgress(job),\n };\n if (job.status === \"completed\") {\n body.video = {\n url: job.video.url || GROK_DEFAULT_VIDEO_URL,\n duration: job.video.duration ?? 0,\n };\n body.usage = {\n cost_in_usd_ticks: Math.round((job.video.cost ?? 0) * GROK_USD_TICKS_PER_USD),\n };\n } else if (job.status === \"failed\") {\n if (job.video.error === \"\" && !job.emptyErrorWarned) {\n job.emptyErrorWarned = true;\n defaults.logger.warn(\n `Video fixture for job ${job.requestId} has an empty error message — using the default`,\n );\n }\n body.code = \"generation_failed\";\n body.error = job.video.error || \"Video generation failed\";\n }\n return body;\n}\n\n// ─── Record mode: live interactive proxy (submit) ────────────────────────────\n\nconst GROK_VIDEO_SUBMIT_PATH = \"/v1/videos/generations\";\n\n/**\n * Proxy an unmatched Grok video submit to the configured upstream and answer\n * the client with a mock-rewritten `{ request_id }`. The upstream lifecycle is\n * then driven interactively by the client's own polls. Returns \"no_upstream\"\n * when record mode has no grok provider URL — the caller falls through to 404.\n */\nasync function proxyGrokVideoSubmit(args: {\n req: http.IncomingMessage;\n res: http.ServerResponse;\n raw: string;\n syntheticReq: ChatCompletionRequest;\n record: RecordConfig;\n journal: Journal;\n defaults: HandlerDefaults;\n jobs: GrokVideoJobMap;\n method: string;\n path: string;\n}): Promise<\"handled\" | \"no_upstream\"> {\n const { req, res, raw, syntheticReq, record, journal, defaults, jobs, method, path } = args;\n\n const upstreamBase = record.providers.grok;\n if (!upstreamBase) {\n defaults.logger.warn(`No upstream URL configured for provider \"grok\" — cannot proxy`);\n return \"no_upstream\";\n }\n\n const proxyError = (msg: string): \"handled\" => {\n defaults.logger.error(`Grok video submit proxy failed: ${msg}`);\n if (res.destroyed || res.writableEnded) return \"handled\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 502,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 502,\n JSON.stringify({ code: \"proxy_error\", error: `Proxy to upstream failed: ${msg}` }),\n );\n return \"handled\";\n };\n\n let submitUrl: URL;\n let upstreamOrigin: string;\n try {\n submitUrl = resolveUpstreamUrl(upstreamBase, GROK_VIDEO_SUBMIT_PATH);\n upstreamOrigin = new URL(upstreamBase).origin;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return proxyError(`Invalid upstream URL: ${upstreamBase} (${msg})`);\n }\n\n defaults.logger.warn(\n `NO FIXTURE MATCH — proxying video submit to ${upstreamBase}${GROK_VIDEO_SUBMIT_PATH}`,\n );\n\n const worldGeneration = jobs.generation;\n\n let fetched: { status: number; contentType: string | null; text: string };\n try {\n const headers = prepareEgressHeaders(req, submitUrl, \"grok\", record.providerKeys?.grok);\n if (!headers) return proxyError(\"No configured provider credential\");\n const upstreamRes = await fetch(submitUrl, {\n method: \"POST\",\n headers,\n body: raw,\n signal: upstreamTimeoutSignal(record),\n });\n fetched = {\n status: upstreamRes.status,\n contentType: upstreamRes.headers.get(\"content-type\"),\n text: await readEnvelopeText(upstreamRes, record),\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : \"Unknown proxy error\";\n return proxyError(msg);\n }\n\n if (fetched.status === 401 || fetched.status === 403) {\n defaults.logger.warn(\n `Upstream rejected the video submit (${fetched.status}) — relaying the upstream status`,\n );\n if (res.destroyed || res.writableEnded) return \"handled\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: fetched.status,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n res.writeHead(fetched.status, { \"Content-Type\": fetched.contentType ?? \"application/json\" });\n res.end(fetched.text);\n return \"handled\";\n }\n\n let upstreamRequestId: string;\n {\n if (fetched.status < 200 || fetched.status >= 300) {\n return proxyError(`Submit ${fetched.status}: ${fetched.text.slice(0, 200)}`);\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(fetched.text);\n } catch {\n return proxyError(`Submit returned non-JSON: ${fetched.text.slice(0, 200)}`);\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return proxyError(\"Submit response is not a JSON object\");\n }\n const envelope = parsed as Record<string, unknown>;\n upstreamRequestId = String(envelope.request_id ?? \"\").trim();\n if (!upstreamRequestId) {\n return proxyError(\"Submit response missing request_id\");\n }\n }\n\n // Construct the poll URL on the configured provider origin (Grok submit\n // returns only request_id, no polling_url — so there is nothing off-origin\n // to validate; we always build it ourselves on the trusted origin).\n void upstreamOrigin;\n const upstreamPollingUrl = resolveUpstreamUrl(\n upstreamBase,\n `/v1/videos/${encodeURIComponent(upstreamRequestId)}`,\n ).toString();\n\n const testId = getTestId(req);\n const matchRequest = defaults.requestTransform\n ? defaults.requestTransform(syntheticReq)\n : syntheticReq;\n const requestId = crypto.randomUUID();\n const job: GrokVideoRecordJob = {\n kind: \"record\",\n requestId,\n status: \"pending\",\n upstreamRequestId,\n upstreamPollingUrl,\n match: buildFixtureMatch(matchRequest, record),\n };\n if (jobs.generation === worldGeneration) {\n jobs.set(`${testId}:${requestId}`, job);\n } else {\n defaults.logger.warn(\n `Grok video submit for upstream request ${upstreamRequestId} completed after a fixtures reset — not inserting the job into the new world (its polls will 404)`,\n );\n }\n\n if (res.destroyed || res.writableEnded) return \"handled\";\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: syntheticReq,\n response: {\n status: 200,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n\n // Embed the testId in the RETURNED request_id (see the replay submit) so\n // record-mode multi-tenant polls resolve via `?testId=`; the upstream poll\n // still uses the bare job.requestId / job.upstreamPollingUrl, untouched.\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ request_id: requestId + testIdSuffix(testId, \"?\") }));\n return \"handled\";\n}\n\n// ─── Record mode: live interactive proxy (status poll + eager capture) ───────\n\n/**\n * Proxy a status poll for a record-mode job 1:1 to the upstream and relay the\n * result with the mock requestId substituted. On `done` the rewritten body is\n * relayed IMMEDIATELY and an eager capture runs (synchronously here — there is\n * NO byte download, only the url/duration/cost from the terminal poll body —\n * but structured to mirror openrouter-video's terminal path). `failed`\n * persists a failed fixture synchronously. `expired` (and any unrepresentable\n * status) pass through with a warn and keep proxying. Every post-await map\n * mutation is identity-guarded. Each successful proxied poll refreshes the TTL.\n */\nasync function proxyGrokVideoRecordPoll(args: {\n req: http.IncomingMessage;\n res: http.ServerResponse;\n job: GrokVideoRecordJob;\n key: string;\n testId: string;\n fixtures: Fixture[];\n journal: Journal;\n defaults: HandlerDefaults;\n jobs: GrokVideoJobMap;\n method: string;\n path: string;\n}): Promise<void> {\n const { req, res, job, key, testId, fixtures, journal, defaults, jobs, method, path } = args;\n const logger = defaults.logger;\n\n const journalProxy = (status: number): void => {\n journal.add({\n method,\n path,\n headers: flattenHeaders(req.headers),\n body: null,\n response: {\n status,\n fixture: null,\n source: \"proxy\",\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n };\n\n const proxyError = (msg: string): void => {\n logger.error(`Grok video poll proxy failed: ${msg}`);\n if (res.destroyed || res.writableEnded) return;\n journalProxy(502);\n writeErrorResponse(\n res,\n 502,\n JSON.stringify({ code: \"proxy_error\", error: `Proxy to upstream failed: ${msg}` }),\n );\n };\n\n const record = defaults.record;\n if (!record) {\n proxyError(\"record mode is no longer configured for an in-flight record job\");\n return;\n }\n\n let fetched: { status: number; contentType: string | null; text: string };\n try {\n const target = new URL(job.upstreamPollingUrl);\n const headers = prepareEgressHeaders(req, target, \"grok\", record.providerKeys?.grok);\n if (!headers) {\n proxyError(\"No configured provider credential\");\n return;\n }\n const upstreamRes = await fetch(job.upstreamPollingUrl, {\n headers,\n signal: upstreamTimeoutSignal(record),\n });\n fetched = {\n status: upstreamRes.status,\n contentType: upstreamRes.headers.get(\"content-type\"),\n text: await readEnvelopeText(upstreamRes, record),\n };\n } catch (err) {\n proxyError(err instanceof Error ? err.message : \"Unknown proxy error\");\n return;\n }\n\n if (fetched.status === 401 || fetched.status === 403) {\n logger.warn(\n `Upstream rejected the status poll for job ${job.upstreamRequestId} (${fetched.status}) — relaying the upstream status`,\n );\n if (res.destroyed || res.writableEnded) return;\n journalProxy(fetched.status);\n res.writeHead(fetched.status, { \"Content-Type\": fetched.contentType ?? \"application/json\" });\n res.end(fetched.text);\n return;\n }\n\n let upstreamBody: Record<string, unknown>;\n {\n if (fetched.status < 200 || fetched.status >= 300) {\n proxyError(`Status ${fetched.status}: ${fetched.text.slice(0, 200)}`);\n return;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(fetched.text);\n } catch {\n proxyError(`Status returned non-JSON: ${fetched.text.slice(0, 200)}`);\n return;\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n proxyError(\"Status response is not a JSON object\");\n return;\n }\n upstreamBody = parsed as Record<string, unknown>;\n }\n\n // Rewrite the relay so no upstream id escapes (request_id → mock id);\n // status/progress/video/usage pass through verbatim.\n const relayBody: Record<string, unknown> = { ...upstreamBody, request_id: job.requestId };\n\n const relayJson = (body: Record<string, unknown>): void => {\n if (res.destroyed || res.writableEnded) return;\n journalProxy(200);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n };\n\n const upstreamStatus = String(upstreamBody.status ?? \"\");\n\n if (upstreamStatus === \"pending\" || upstreamStatus === \"in_progress\") {\n if (jobs.get(key) === job) {\n if (!job.capturing && job.status !== \"completed\" && job.status !== \"failed\") {\n job.status = upstreamStatus === \"in_progress\" ? \"in_progress\" : \"pending\";\n }\n jobs.set(key, job); // TTL refresh\n }\n relayJson(relayBody);\n return;\n }\n\n if (upstreamStatus === \"done\") {\n if (record.proxyOnly) {\n if (jobs.get(key) === job) {\n job.status = \"completed\";\n jobs.set(key, job); // TTL refresh\n }\n relayJson(relayBody);\n return;\n }\n\n if (job.capturing || jobs.get(key) !== job) {\n if (jobs.get(key) === job) jobs.set(key, job); // TTL refresh\n relayJson(relayBody);\n return;\n }\n\n // Open the capturing window SYNCHRONOUSLY. There is no byte download, so\n // the capture is synchronous, but mirror the structure: latch capturing,\n // set terminal status, relay, persist, mutate to a terminal replay job.\n job.capturing = true;\n job.status = \"completed\";\n jobs.set(key, job);\n\n relayJson(relayBody);\n\n captureGrokVideoRecordFixture({\n job,\n key,\n testId,\n fixtures,\n defaults,\n jobs,\n record,\n upstreamBody,\n });\n return;\n }\n\n if (upstreamStatus === \"failed\") {\n if (record.proxyOnly) {\n if (jobs.get(key) === job) {\n job.status = \"failed\";\n jobs.set(key, job); // TTL refresh\n }\n relayJson(relayBody);\n return;\n }\n\n if (jobs.get(key) !== job) {\n relayJson(relayBody);\n return;\n }\n\n const rawError = upstreamBody.error;\n let error: string | undefined;\n if (typeof rawError === \"string\" && rawError) {\n error = rawError;\n } else if (rawError !== null && typeof rawError === \"object\" && !Array.isArray(rawError)) {\n const message = (rawError as Record<string, unknown>).message;\n if (typeof message === \"string\" && message) error = message;\n }\n if (error === undefined && rawError !== undefined && rawError !== null) {\n logger.warn(\n `Upstream video job ${job.upstreamRequestId} failed with an unusable error value (${JSON.stringify(rawError).slice(0, 100)}) — recording the fixture without an error message (replay serves the default)`,\n );\n }\n const video: VideoResponse[\"video\"] = {\n id: job.upstreamRequestId,\n status: \"failed\",\n ...(error !== undefined ? { error } : {}),\n };\n const persistResult = persistFixture({\n record,\n providerKey: \"grok\",\n testId,\n fixture: { match: job.match, response: { video } },\n fixtures,\n logger,\n });\n if (persistResult.kind === \"failed\" && !res.headersSent) {\n res.setHeader(\"X-AIMock-Record-Error\", sanitizeHeaderValue(persistResult.error));\n }\n jobs.set(key, {\n kind: \"replay\",\n requestId: job.requestId,\n status: \"failed\",\n pollCount: 0,\n pollsBeforeInProgress: 0,\n pollsBeforeCompleted: 0,\n video: { ...video },\n });\n relayJson(relayBody);\n return;\n }\n\n // expired / anything else: terminal upstream states not representable in\n // VideoResponse.status — pass through (id rewritten), record nothing, keep\n // proxying.\n logger.warn(\n `Upstream video job ${job.upstreamRequestId} reported status \"${upstreamStatus}\" — not representable in fixture video.status; passing through without recording`,\n );\n if (jobs.get(key) === job) {\n jobs.set(key, job);\n }\n relayJson(relayBody);\n}\n\n/**\n * Capture a completed Grok record job into a fixture (PERSIST only — NO byte\n * download): builds `video` from the terminal poll body (id = upstream\n * request_id, status = stored \"completed\", url/duration/cost), persists it, and\n * mutates the map entry into a terminal replay job. World-generation guard\n * before persist (a fixtures reset mid-flight invalidates the world).\n */\nfunction captureGrokVideoRecordFixture(args: {\n job: GrokVideoRecordJob;\n key: string;\n testId: string;\n fixtures: Fixture[];\n defaults: HandlerDefaults;\n jobs: GrokVideoJobMap;\n record: RecordConfig;\n upstreamBody: Record<string, unknown>;\n}): void {\n const { job, key, testId, fixtures, defaults, jobs, record, upstreamBody } = args;\n const logger = defaults.logger;\n\n try {\n const upstreamVideo =\n upstreamBody.video !== null &&\n typeof upstreamBody.video === \"object\" &&\n !Array.isArray(upstreamBody.video)\n ? (upstreamBody.video as Record<string, unknown>)\n : undefined;\n const rawUrl = upstreamVideo?.url;\n const url = typeof rawUrl === \"string\" && rawUrl ? rawUrl : GROK_DEFAULT_VIDEO_URL;\n const rawDuration = upstreamVideo?.duration;\n const duration = typeof rawDuration === \"number\" ? rawDuration : undefined;\n\n const usage =\n upstreamBody.usage !== null &&\n typeof upstreamBody.usage === \"object\" &&\n !Array.isArray(upstreamBody.usage)\n ? (upstreamBody.usage as Record<string, unknown>)\n : undefined;\n const rawTicks = usage?.cost_in_usd_ticks;\n const cost = typeof rawTicks === \"number\" ? rawTicks / GROK_USD_TICKS_PER_USD : undefined;\n\n const video: VideoResponse[\"video\"] = {\n id: job.upstreamRequestId,\n status: \"completed\",\n url,\n ...(duration !== undefined ? { duration } : {}),\n ...(cost !== undefined ? { cost } : {}),\n };\n\n // World-generation guard: a fixtures reset clears the job map, so map\n // identity is a valid proxy for \"same world\".\n if (jobs.get(key) !== job) {\n logger.warn(\n `Grok video capture for job ${job.upstreamRequestId} discarded: the job map no longer holds this job (fixtures reset or TTL eviction) — nothing persisted`,\n );\n return;\n }\n persistFixture({\n record,\n providerKey: \"grok\",\n testId,\n fixture: { match: job.match, response: { video } },\n fixtures,\n logger,\n });\n\n if (jobs.get(key) === job) {\n jobs.set(key, {\n kind: \"replay\",\n requestId: job.requestId,\n status: \"completed\",\n pollCount: 0,\n pollsBeforeInProgress: 0,\n pollsBeforeCompleted: 0,\n video: { ...video },\n });\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.error(\n `Grok video capture for job ${job.upstreamRequestId} failed unexpectedly (${msg}) — fixture not persisted; the job keeps proxying live`,\n );\n } finally {\n // Reset unconditionally (defense-in-depth): the flag lives on this `job`\n // object, so clearing it is always safe — harmless on an orphaned/replaced\n // job (the success path swaps in a fresh replay entry; the old object is\n // discarded either way). The previous `jobs.get(key) === job` guard was a\n // latent landmine: if capture were ever made async/detached, an eviction\n // mid-flight would leave `capturing=true` stuck forever and silently block\n // re-capture. Unconditional reset removes that hazard.\n job.capturing = false;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"grok-video.d.cts","names":[],"sources":["../src/grok-video.ts"],"sourcesContent":[],"mappings":";;;;;;cAyEa,sBAAA;KAGR,eAAA;AAHL,UAKU,kBAAA,CALyB;EAG9B,IAAA,EAAA,QAAA;EAEK,SAAA,EAAA,MAAA;EAAkB,MAAA,EAGlB,eAHkB;;WAWnB,EAAA,MAAA;EAAa;EAcZ,qBAAkB,EAAA,MAAA;EAAA;sBAKlB,EAAA,MAAA;;EAMM,KAAA,EAzBP,aAyBO,CAAA,OAAA,CAAA;EASJ;EAAY,gBAAA,CAAA,EAAA,OAAA;;;;AAcxB;;;;;AAwHA;;UA1JU,kBAAA,CA2JE;MACL,EAAA,QAAK;;WAGD,EAAA,MAAA;;QAEa,EA5Jd,eA4JmB;;mBAE1B,EAAA,MAAA;EAAO;EA6SY,kBAAA,EAAA,MAAqB;EAAA;OACpC,EAtcE,OAscG,CAAA,OAAA,CAAA;;;;;;WAOA,CAAA,EAAA,OAAA;;AAET,KAtcS,YAAA,GAAe,kBAscxB,GAtc6C,kBAsc7C;;;;;;;;cAxbU,eAAA;;;;oBAQO;wBAUI;;;;;iBAsGF,qBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,+BACrB,kBACL;iBA6SmB,qBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,sCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,mCACjB,8BACG,gBACZ"}
1
+ {"version":3,"file":"grok-video.d.cts","names":[],"sources":["../src/grok-video.ts"],"sourcesContent":[],"mappings":";;;;;;cAyEa,sBAAA;KAGR,eAAA;AAHL,UAKU,kBAAA,CALyB;EAG9B,IAAA,EAAA,QAAA;EAEK,SAAA,EAAA,MAAA;EAAkB,MAAA,EAGlB,eAHkB;;WAWnB,EAAA,MAAA;EAAa;EAcZ,qBAAkB,EAAA,MAAA;EAAA;sBAKlB,EAAA,MAAA;;EAMM,KAAA,EAzBP,aAyBO,CAAA,OAAA,CAAA;EASJ;EAAY,gBAAA,CAAA,EAAA,OAAA;;;;AAcxB;;;;;AAwHA;;UA1JU,kBAAA,CA2JE;MACL,EAAA,QAAK;;WAGD,EAAA,MAAA;;QAEa,EA5Jd,eA4JmB;;mBAE1B,EAAA,MAAA;EAAO;EA8SY,kBAAA,EAAA,MAAqB;EAAA;OACpC,EAvcE,OAucG,CAAA,OAAA,CAAA;;;;;;WAOA,CAAA,EAAA,OAAA;;AAET,KAvcS,YAAA,GAAe,kBAucxB,GAvc6C,kBAuc7C;;;;;;;;cAzbU,eAAA;;;;oBAQO;wBAUI;;;;;iBAsGF,qBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,+BACrB,kBACL;iBA8SmB,qBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,sCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,mCACjB,8BACG,gBACZ"}
@@ -1 +1 @@
1
- {"version":3,"file":"grok-video.d.ts","names":[],"sources":["../src/grok-video.ts"],"sourcesContent":[],"mappings":";;;;;;cAyEa,sBAAA;KAGR,eAAA;AAHL,UAKU,kBAAA,CALyB;EAG9B,IAAA,EAAA,QAAA;EAEK,SAAA,EAAA,MAAA;EAAkB,MAAA,EAGlB,eAHkB;;WAWnB,EAAA,MAAA;EAAa;EAcZ,qBAAkB,EAAA,MAAA;EAAA;sBAKlB,EAAA,MAAA;;EAMM,KAAA,EAzBP,aAyBO,CAAA,OAAA,CAAA;EASJ;EAAY,gBAAA,CAAA,EAAA,OAAA;;;;AAcxB;;;;;AAwHA;;UA1JU,kBAAA,CA2JE;MACL,EAAA,QAAK;;WAGD,EAAA,MAAA;;QAEa,EA5Jd,eA4JmB;;mBAE1B,EAAA,MAAA;EAAO;EA6SY,kBAAA,EAAA,MAAqB;EAAA;OACpC,EAtcE,OAscG,CAAA,OAAA,CAAA;;;;;;WAOA,CAAA,EAAA,OAAA;;AAET,KAtcS,YAAA,GAAe,kBAscxB,GAtc6C,kBAsc7C;;;;;;;;cAxbU,eAAA;;;;oBAQO;wBAUI;;;;;iBAsGF,qBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,+BACrB,kBACL;iBA6SmB,qBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,sCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,mCACjB,8BACG,gBACZ"}
1
+ {"version":3,"file":"grok-video.d.ts","names":[],"sources":["../src/grok-video.ts"],"sourcesContent":[],"mappings":";;;;;;cAyEa,sBAAA;KAGR,eAAA;AAHL,UAKU,kBAAA,CALyB;EAG9B,IAAA,EAAA,QAAA;EAEK,SAAA,EAAA,MAAA;EAAkB,MAAA,EAGlB,eAHkB;;WAWnB,EAAA,MAAA;EAAa;EAcZ,qBAAkB,EAAA,MAAA;EAAA;sBAKlB,EAAA,MAAA;;EAMM,KAAA,EAzBP,aAyBO,CAAA,OAAA,CAAA;EASJ;EAAY,gBAAA,CAAA,EAAA,OAAA;;;;AAcxB;;;;;AAwHA;;UA1JU,kBAAA,CA2JE;MACL,EAAA,QAAK;;WAGD,EAAA,MAAA;;QAEa,EA5Jd,eA4JmB;;mBAE1B,EAAA,MAAA;EAAO;EA8SY,kBAAA,EAAA,MAAqB;EAAA;OACpC,EAvcE,OAucG,CAAA,OAAA,CAAA;;;;;;WAOA,CAAA,EAAA,OAAA;;AAET,KAvcS,YAAA,GAAe,kBAucxB,GAvc6C,kBAuc7C;;;;;;;;cAzbU,eAAA;;;;oBAQO;wBAUI;;;;;iBAsGF,qBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,+BACrB,kBACL;iBA8SmB,qBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,sCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,mCACjB,8BACG,gBACZ"}