@copilotkit/aimock 1.33.0 → 1.35.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 (158) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +30 -0
  4. package/README.md +6 -1
  5. package/dist/agui-types.d.ts.map +1 -1
  6. package/dist/bedrock-converse.cjs +112 -10
  7. package/dist/bedrock-converse.cjs.map +1 -1
  8. package/dist/bedrock-converse.d.cts.map +1 -1
  9. package/dist/bedrock-converse.d.ts.map +1 -1
  10. package/dist/bedrock-converse.js +114 -12
  11. package/dist/bedrock-converse.js.map +1 -1
  12. package/dist/bedrock.cjs +143 -6
  13. package/dist/bedrock.cjs.map +1 -1
  14. package/dist/bedrock.d.cts.map +1 -1
  15. package/dist/bedrock.d.ts.map +1 -1
  16. package/dist/bedrock.js +145 -8
  17. package/dist/bedrock.js.map +1 -1
  18. package/dist/cohere.cjs +91 -9
  19. package/dist/cohere.cjs.map +1 -1
  20. package/dist/cohere.d.cts.map +1 -1
  21. package/dist/cohere.d.ts.map +1 -1
  22. package/dist/cohere.js +93 -11
  23. package/dist/cohere.js.map +1 -1
  24. package/dist/config-loader.d.ts.map +1 -1
  25. package/dist/fixture-loader.cjs +121 -20
  26. package/dist/fixture-loader.cjs.map +1 -1
  27. package/dist/fixture-loader.d.cts.map +1 -1
  28. package/dist/fixture-loader.d.ts.map +1 -1
  29. package/dist/fixture-loader.js +121 -20
  30. package/dist/fixture-loader.js.map +1 -1
  31. package/dist/gemini-interactions.cjs +146 -108
  32. package/dist/gemini-interactions.cjs.map +1 -1
  33. package/dist/gemini-interactions.d.cts.map +1 -1
  34. package/dist/gemini-interactions.d.ts.map +1 -1
  35. package/dist/gemini-interactions.js +147 -109
  36. package/dist/gemini-interactions.js.map +1 -1
  37. package/dist/gemini.cjs +72 -7
  38. package/dist/gemini.cjs.map +1 -1
  39. package/dist/gemini.d.cts.map +1 -1
  40. package/dist/gemini.d.ts.map +1 -1
  41. package/dist/gemini.js +74 -9
  42. package/dist/gemini.js.map +1 -1
  43. package/dist/grok-video.cjs +834 -0
  44. package/dist/grok-video.cjs.map +1 -0
  45. package/dist/grok-video.d.cts +74 -0
  46. package/dist/grok-video.d.cts.map +1 -0
  47. package/dist/grok-video.d.ts +74 -0
  48. package/dist/grok-video.d.ts.map +1 -0
  49. package/dist/grok-video.js +829 -0
  50. package/dist/grok-video.js.map +1 -0
  51. package/dist/helpers.cjs +181 -2
  52. package/dist/helpers.cjs.map +1 -1
  53. package/dist/helpers.d.cts +33 -3
  54. package/dist/helpers.d.cts.map +1 -1
  55. package/dist/helpers.d.ts +33 -3
  56. package/dist/helpers.d.ts.map +1 -1
  57. package/dist/helpers.js +181 -3
  58. package/dist/helpers.js.map +1 -1
  59. package/dist/index.cjs +11 -0
  60. package/dist/index.d.cts +5 -3
  61. package/dist/index.d.ts +5 -3
  62. package/dist/index.js +4 -2
  63. package/dist/messages.cjs +100 -11
  64. package/dist/messages.cjs.map +1 -1
  65. package/dist/messages.d.cts.map +1 -1
  66. package/dist/messages.d.ts.map +1 -1
  67. package/dist/messages.js +102 -13
  68. package/dist/messages.js.map +1 -1
  69. package/dist/metrics.cjs +11 -1
  70. package/dist/metrics.cjs.map +1 -1
  71. package/dist/metrics.d.cts.map +1 -1
  72. package/dist/metrics.d.ts.map +1 -1
  73. package/dist/metrics.js +8 -1
  74. package/dist/metrics.js.map +1 -1
  75. package/dist/ollama.cjs +77 -7
  76. package/dist/ollama.cjs.map +1 -1
  77. package/dist/ollama.d.cts.map +1 -1
  78. package/dist/ollama.d.ts.map +1 -1
  79. package/dist/ollama.js +79 -9
  80. package/dist/ollama.js.map +1 -1
  81. package/dist/openrouter-video.cjs +19 -138
  82. package/dist/openrouter-video.cjs.map +1 -1
  83. package/dist/openrouter-video.d.cts.map +1 -1
  84. package/dist/openrouter-video.d.ts.map +1 -1
  85. package/dist/openrouter-video.js +1 -120
  86. package/dist/openrouter-video.js.map +1 -1
  87. package/dist/recorder.cjs +12 -9
  88. package/dist/recorder.cjs.map +1 -1
  89. package/dist/recorder.js +12 -9
  90. package/dist/recorder.js.map +1 -1
  91. package/dist/responses.cjs +137 -59
  92. package/dist/responses.cjs.map +1 -1
  93. package/dist/responses.d.cts +2 -2
  94. package/dist/responses.d.cts.map +1 -1
  95. package/dist/responses.d.ts +2 -2
  96. package/dist/responses.d.ts.map +1 -1
  97. package/dist/responses.js +139 -61
  98. package/dist/responses.js.map +1 -1
  99. package/dist/router.cjs +236 -33
  100. package/dist/router.cjs.map +1 -1
  101. package/dist/router.d.cts +54 -2
  102. package/dist/router.d.cts.map +1 -1
  103. package/dist/router.d.ts +54 -2
  104. package/dist/router.d.ts.map +1 -1
  105. package/dist/router.js +236 -34
  106. package/dist/router.js.map +1 -1
  107. package/dist/server.cjs +163 -21
  108. package/dist/server.cjs.map +1 -1
  109. package/dist/server.d.cts +4 -0
  110. package/dist/server.d.cts.map +1 -1
  111. package/dist/server.d.ts +4 -0
  112. package/dist/server.d.ts.map +1 -1
  113. package/dist/server.js +166 -24
  114. package/dist/server.js.map +1 -1
  115. package/dist/stream-collapse.cjs +355 -79
  116. package/dist/stream-collapse.cjs.map +1 -1
  117. package/dist/stream-collapse.d.cts +27 -4
  118. package/dist/stream-collapse.d.cts.map +1 -1
  119. package/dist/stream-collapse.d.ts +27 -4
  120. package/dist/stream-collapse.d.ts.map +1 -1
  121. package/dist/stream-collapse.js +355 -79
  122. package/dist/stream-collapse.js.map +1 -1
  123. package/dist/types.d.cts +104 -7
  124. package/dist/types.d.cts.map +1 -1
  125. package/dist/types.d.ts +104 -7
  126. package/dist/types.d.ts.map +1 -1
  127. package/dist/vector-types.d.ts.map +1 -1
  128. package/dist/veo-video.cjs +794 -0
  129. package/dist/veo-video.cjs.map +1 -0
  130. package/dist/veo-video.d.cts +50 -0
  131. package/dist/veo-video.d.cts.map +1 -0
  132. package/dist/veo-video.d.ts +50 -0
  133. package/dist/veo-video.d.ts.map +1 -0
  134. package/dist/veo-video.js +789 -0
  135. package/dist/veo-video.js.map +1 -0
  136. package/dist/video-proxy-shared.cjs +145 -0
  137. package/dist/video-proxy-shared.cjs.map +1 -0
  138. package/dist/video-proxy-shared.js +140 -0
  139. package/dist/video-proxy-shared.js.map +1 -0
  140. package/dist/ws-gemini-live.cjs +96 -3
  141. package/dist/ws-gemini-live.cjs.map +1 -1
  142. package/dist/ws-gemini-live.d.cts.map +1 -1
  143. package/dist/ws-gemini-live.d.ts.map +1 -1
  144. package/dist/ws-gemini-live.js +97 -4
  145. package/dist/ws-gemini-live.js.map +1 -1
  146. package/dist/ws-realtime.cjs +281 -4
  147. package/dist/ws-realtime.cjs.map +1 -1
  148. package/dist/ws-realtime.d.cts.map +1 -1
  149. package/dist/ws-realtime.d.ts.map +1 -1
  150. package/dist/ws-realtime.js +282 -5
  151. package/dist/ws-realtime.js.map +1 -1
  152. package/dist/ws-responses.cjs +1 -1
  153. package/dist/ws-responses.cjs.map +1 -1
  154. package/dist/ws-responses.js +1 -1
  155. package/dist/ws-responses.js.map +1 -1
  156. package/fixtures/examples/llm/blocks-tool-first.json +13 -0
  157. package/package.json +1 -1
  158. package/skills/write-fixtures/SKILL.md +19 -1
@@ -1 +1 @@
1
- {"version":3,"file":"gemini-interactions.cjs","names":["generateToolCallId","calculateDelay","delay","flattenHeaders","getContext","getTestId","matchFixtureDiagnostic","applyChaos","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","proxyAndRecord","resolveResponse","isErrorResponse","isContentWithToolCallsResponse","extractOverrides","createInterruptionSignal","isTextResponse","isToolCallResponse"],"sources":["../src/gemini-interactions.ts"],"sourcesContent":["/**\n * Google Gemini Interactions API support.\n *\n * Translates incoming Interactions requests into the ChatCompletionRequest\n * format used by the fixture router, and converts fixture responses back\n * into the Gemini Interactions format — either a single JSON response or\n * an SSE stream with event_type-based framing.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n RecordedTimings,\n ResponseOverrides,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n extractOverrides,\n generateToolCallId,\n flattenHeaders,\n getTestId,\n getContext,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } 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// ─── Interactions request types ────────────────────────────────────────────\n\ninterface InteractionsContentBlock {\n type: string;\n text?: string;\n name?: string;\n call_id?: string;\n id?: string;\n arguments?: Record<string, unknown>;\n output?: unknown;\n result?: unknown;\n}\n\ninterface InteractionsTurn {\n role: string;\n content?: InteractionsContentBlock[];\n parts?: InteractionsContentBlock[];\n}\n\n/**\n * Top-level Step envelope accepted by the live Gemini Interactions API.\n * The SDK's TypeScript union does not include Step[], but the wire contract\n * does — clients following the live API send these at the top level of `input`.\n * Discriminated by `type`; no `role` field (distinguishes from Turn[]).\n */\ninterface InteractionsStep {\n type: string;\n content?: InteractionsContentBlock[];\n call_id?: string;\n id?: string;\n name?: string;\n result?: unknown;\n output?: unknown;\n is_error?: boolean;\n signature?: string;\n}\n\n/** Step types whose payload is a tool/agent result keyed by call_id. */\nconst STEP_RESULT_TYPES = new Set<string>([\n \"function_result\",\n \"code_execution_result\",\n \"url_context_result\",\n \"google_search_result\",\n \"google_maps_result\",\n \"mcp_server_tool_result\",\n \"file_search_result\",\n]);\n\n/** All recognized top-level Step types (used as the Step[] discriminator). */\nconst STEP_TYPES = new Set<string>([\"user_input\", \"model_output\", ...STEP_RESULT_TYPES]);\n\ninterface InteractionsFunctionTool {\n type: \"function\";\n name: string;\n description?: string;\n parameters?: object;\n}\n\ninterface InteractionsRequest {\n model?: string;\n input?: string | InteractionsTurn[] | InteractionsStep[] | InteractionsContentBlock[];\n system_instruction?: string;\n tools?: InteractionsFunctionTool[];\n generation_config?: {\n temperature?: number;\n max_output_tokens?: number;\n [key: string]: unknown;\n };\n stream?: boolean;\n previous_interaction_id?: string;\n [key: string]: unknown;\n}\n\n// ─── Input conversion: Interactions → ChatCompletionRequest ───────────────\n\nexport function geminiInteractionsToCompletionRequest(\n req: InteractionsRequest,\n): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n const model = req.model ?? \"gemini-2.5-flash\";\n\n // system_instruction → system message\n if (req.system_instruction) {\n messages.push({ role: \"system\", content: req.system_instruction });\n }\n\n // Parse input\n if (req.input !== undefined) {\n if (typeof req.input === \"string\") {\n // Simple string input → single user message\n messages.push({ role: \"user\", content: req.input });\n } else if (Array.isArray(req.input)) {\n // Could be Turn[], Step[], or Content[]\n const firstItem = req.input[0] as\n | InteractionsTurn\n | InteractionsStep\n | InteractionsContentBlock\n | undefined;\n const isStepArray =\n !!firstItem &&\n !(\"role\" in firstItem) &&\n typeof firstItem.type === \"string\" &&\n STEP_TYPES.has(firstItem.type);\n\n if (firstItem && \"role\" in firstItem) {\n // Turn[] format\n for (const turn of req.input as InteractionsTurn[]) {\n const role = turn.role === \"model\" ? \"assistant\" : turn.role;\n const blocks = turn.content ?? turn.parts;\n if (!blocks || blocks.length === 0) {\n if (role === \"user\" || role === \"assistant\") {\n messages.push({ role: role as \"user\" | \"assistant\", content: \"\" });\n }\n continue;\n }\n\n // Check for function_call or function_result parts\n const funcCallParts = blocks.filter((p) => p.type === \"function_call\");\n const funcResultParts = blocks.filter((p) => p.type === \"function_result\");\n const textParts = blocks.filter((p) => p.type === \"text\");\n\n if (funcCallParts.length > 0) {\n // Assistant tool call message\n const textContent = textParts.map((p) => p.text ?? \"\").join(\"\");\n messages.push({\n role: \"assistant\",\n content: textContent || null,\n tool_calls: funcCallParts.map((p) => ({\n id: p.id ?? p.call_id ?? generateToolCallId(),\n type: \"function\" as const,\n function: {\n name: p.name ?? \"\",\n arguments: JSON.stringify(p.arguments ?? {}),\n },\n })),\n });\n } else if (funcResultParts.length > 0) {\n // Tool response messages\n for (const part of funcResultParts) {\n const resultValue = part.result ?? part.output;\n messages.push({\n role: \"tool\",\n content:\n typeof resultValue === \"string\" ? resultValue : JSON.stringify(resultValue ?? \"\"),\n tool_call_id: part.call_id ?? part.id ?? \"\",\n });\n }\n // Any text parts alongside → separate user message\n if (textParts.length > 0) {\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n if (text) {\n messages.push({ role: \"user\", content: text });\n }\n }\n } else {\n // Text-only turn\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n if (role === \"user\" || role === \"assistant\" || role === \"system\") {\n messages.push({\n role: role as \"user\" | \"assistant\" | \"system\",\n content: text,\n });\n }\n }\n }\n } else if (isStepArray) {\n // Step[] format — the wire contract Google's /v1beta/interactions accepts.\n for (const step of req.input as InteractionsStep[]) {\n if (step.type === \"user_input\") {\n const text = (step.content ?? [])\n .filter((p) => p.type === \"text\")\n .map((p) => p.text ?? \"\")\n .join(\"\");\n messages.push({ role: \"user\", content: text });\n } else if (step.type === \"model_output\") {\n const blocks = step.content ?? [];\n const funcCallParts = blocks.filter((p) => p.type === \"function_call\");\n const textParts = blocks.filter((p) => p.type === \"text\");\n const textContent = textParts.map((p) => p.text ?? \"\").join(\"\");\n\n if (funcCallParts.length > 0) {\n messages.push({\n role: \"assistant\",\n content: textContent || null,\n tool_calls: funcCallParts.map((p) => ({\n id: p.id ?? p.call_id ?? generateToolCallId(),\n type: \"function\" as const,\n function: {\n name: p.name ?? \"\",\n arguments: JSON.stringify(p.arguments ?? {}),\n },\n })),\n });\n } else {\n messages.push({ role: \"assistant\", content: textContent });\n }\n } else if (STEP_RESULT_TYPES.has(step.type)) {\n const resultValue = step.result ?? step.output;\n messages.push({\n role: \"tool\",\n content:\n typeof resultValue === \"string\" ? resultValue : JSON.stringify(resultValue ?? \"\"),\n tool_call_id: step.call_id ?? step.id ?? \"\",\n });\n }\n }\n } else {\n // Content[] format — single user message with content blocks\n const textParts = (req.input as InteractionsContentBlock[]).filter(\n (p) => p.type === \"text\",\n );\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n messages.push({ role: \"user\", content: text || \"\" });\n }\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n const funcTools = req.tools.filter((t) => t.type === \"function\");\n if (funcTools.length > 0) {\n tools = funcTools.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n },\n }));\n }\n }\n\n return {\n model,\n messages,\n stream: req.stream !== false, // default true\n temperature: req.generation_config?.temperature,\n max_tokens: req.generation_config?.max_output_tokens,\n tools,\n };\n}\n\n// ─── Interaction ID generation ────────────────────────────────────────────\n\nlet interactionCounter = 0;\n\nexport function resetInteractionCounter(): void {\n interactionCounter = 0;\n}\n\nfunction nextInteractionId(): string {\n return `aimock-int-${interactionCounter++}`;\n}\n\n// ─── Usage helpers ────────────────────────────────────────────────────────\n\nfunction interactionsUsage(overrides?: ResponseOverrides): {\n total_input_tokens: number;\n total_output_tokens: number;\n total_tokens: number;\n} {\n if (!overrides?.usage) return { total_input_tokens: 0, total_output_tokens: 0, total_tokens: 0 };\n const input =\n overrides.usage.input_tokens ??\n overrides.usage.prompt_tokens ??\n overrides.usage.promptTokenCount ??\n 0;\n const output =\n overrides.usage.output_tokens ??\n overrides.usage.completion_tokens ??\n overrides.usage.candidatesTokenCount ??\n 0;\n const total = overrides.usage.total_tokens ?? overrides.usage.totalTokenCount ?? input + output;\n return {\n total_input_tokens: input,\n total_output_tokens: output,\n total_tokens: total,\n };\n}\n\n// ─── Response building: fixture → Interactions format ─────────────────────\n\nexport function buildInteractionsTextResponse(\n content: string,\n model: string,\n interactionId: string,\n overrides?: ResponseOverrides,\n): object {\n return {\n id: interactionId,\n status: \"completed\",\n model: overrides?.model ?? model,\n role: \"model\",\n outputs: [{ type: \"text\", text: content }],\n usage: interactionsUsage(overrides),\n };\n}\n\nexport function buildInteractionsToolCallResponse(\n toolCalls: ToolCall[],\n model: string,\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): object {\n return {\n id: interactionId,\n status: \"requires_action\",\n model: overrides?.model ?? model,\n role: \"model\",\n outputs: toolCalls.map((tc) => {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n return {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n };\n }),\n usage: interactionsUsage(overrides),\n };\n}\n\nexport function buildInteractionsContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): object {\n const outputs: object[] = [{ type: \"text\", text: content }];\n for (const tc of toolCalls) {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n outputs.push({\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n });\n }\n\n return {\n id: interactionId,\n status: \"requires_action\",\n model: overrides?.model ?? model,\n role: \"model\",\n outputs,\n usage: interactionsUsage(overrides),\n };\n}\n\nfunction buildInteractionsErrorResponse(message: string, code?: string): object {\n return {\n error: {\n code: code ?? \"INVALID_ARGUMENT\",\n message,\n },\n };\n}\n\n// ─── SSE event builders ──────────────────────────────────────────────────\n\ninterface InteractionsSSEEvent {\n event_type: string;\n [key: string]: unknown;\n}\n\nlet eventIdCounter = 0;\n\nexport function resetEventIdCounter(): void {\n eventIdCounter = 0;\n}\n\nfunction nextEventId(): string {\n return `evt_${++eventIdCounter}`;\n}\n\nexport function buildInteractionsTextSSEEvents(\n content: string,\n interactionId: string,\n chunkSize: number,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.start\n events.push({\n event_type: \"interaction.start\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // content.start\n events.push({\n event_type: \"content.start\",\n index: 0,\n content: { type: \"text\" },\n event_id: nextEventId(),\n });\n\n // content.delta(s)\n if (content.length === 0) {\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: \"\" },\n event_id: nextEventId(),\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: slice },\n event_id: nextEventId(),\n });\n }\n }\n\n // content.stop\n events.push({\n event_type: \"content.stop\",\n index: 0,\n event_id: nextEventId(),\n });\n\n // interaction.complete\n events.push({\n event_type: \"interaction.complete\",\n interaction: {\n id: interactionId,\n status: \"completed\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\nexport function buildInteractionsToolCallSSEEvents(\n toolCalls: ToolCall[],\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.start\n events.push({\n event_type: \"interaction.start\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // Each tool call gets its own content.start/delta/stop bracket\n for (let idx = 0; idx < toolCalls.length; idx++) {\n const tc = toolCalls[idx];\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n\n events.push({\n event_type: \"content.start\",\n index: idx,\n content: { type: \"function_call\" },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.delta\",\n index: idx,\n delta: {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.stop\",\n index: idx,\n event_id: nextEventId(),\n });\n }\n\n // interaction.complete\n events.push({\n event_type: \"interaction.complete\",\n interaction: {\n id: interactionId,\n status: \"requires_action\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\nexport function buildInteractionsContentWithToolCallsSSEEvents(\n content: string,\n toolCalls: ToolCall[],\n interactionId: string,\n chunkSize: number,\n logger: Logger,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.start\n events.push({\n event_type: \"interaction.start\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // Text content at index 0\n events.push({\n event_type: \"content.start\",\n index: 0,\n content: { type: \"text\" },\n event_id: nextEventId(),\n });\n\n if (content.length === 0) {\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: \"\" },\n event_id: nextEventId(),\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n event_type: \"content.delta\",\n index: 0,\n delta: { type: \"text\", text: slice },\n event_id: nextEventId(),\n });\n }\n }\n\n events.push({\n event_type: \"content.stop\",\n index: 0,\n event_id: nextEventId(),\n });\n\n // Tool calls at index 1+\n for (let i = 0; i < toolCalls.length; i++) {\n const tc = toolCalls[i];\n const idx = i + 1; // offset by 1 because text is index 0\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n\n events.push({\n event_type: \"content.start\",\n index: idx,\n content: { type: \"function_call\" },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.delta\",\n index: idx,\n delta: {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"content.stop\",\n index: idx,\n event_id: nextEventId(),\n });\n }\n\n // interaction.complete\n events.push({\n event_type: \"interaction.complete\",\n interaction: {\n id: interactionId,\n status: \"requires_action\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\n// ─── SSE writer for Interactions streaming ────────────────────────────────\n\ninterface InteractionsStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nexport async function writeGeminiInteractionsSSEStream(\n res: http.ServerResponse,\n events: InteractionsSSEEvent[],\n optionsOrLatency?: number | InteractionsStreamOptions,\n): Promise<boolean> {\n const opts: InteractionsStreamOptions =\n typeof optionsOrLatency === \"number\" ? { latency: optionsOrLatency } : (optionsOrLatency ?? {});\n const latency = opts.latency ?? 0;\n const profile = opts.streamingProfile;\n const { recordedTimings, replaySpeed } = opts;\n const signal = opts.signal;\n const onChunkSent = opts.onChunkSent;\n\n if (res.writableEnded) return true;\n res.setHeader(\"Content-Type\", \"text/event-stream\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"keep-alive\");\n\n let chunkIndex = 0;\n for (const event of events) {\n const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);\n if (chunkDelay > 0) await delay(chunkDelay, signal);\n if (signal?.aborted) return false;\n if (res.writableEnded) return true;\n // Data-only SSE (no event: prefix, no [DONE])\n res.write(`data: ${JSON.stringify(event)}\\n\\n`);\n // Only count content deltas for truncateAfterChunks — framing events\n // (interaction.start, content.start, content.stop, interaction.complete)\n // should not consume chunk budget or trigger the chunk-sent callback.\n if (event.event_type === \"content.delta\") {\n onChunkSent?.();\n chunkIndex++;\n }\n if (signal?.aborted) return false;\n }\n\n if (!res.writableEnded) {\n res.end();\n }\n return true;\n}\n\n// ─── Request handler ──────────────────────────────────────────────────────\n\nexport async function handleGeminiInteractions(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n const urlPath = req.url ?? \"/v1beta/interactions\";\n\n let interactionsReq: InteractionsRequest;\n try {\n interactionsReq = JSON.parse(raw) as InteractionsRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify(\n buildInteractionsErrorResponse(`Malformed JSON body: ${detail}`, \"INVALID_ARGUMENT\"),\n ),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = geminiInteractionsToCompletionRequest(interactionsReq);\n // Keep \"chat\" rather than \"gemini-interactions\" — the router's endpoint\n // compatibility filter (router.ts) treats \"chat\" as a pass-through that\n // matches any unendpointed fixture. Switching to \"gemini-interactions\"\n // would make the request fall into the multimedia guard branch, preventing\n // generic chat fixtures from matching and breaking existing users. The\n // recorder would also start emitting `endpoint: \"gemini-interactions\"` in\n // recorded fixtures, creating a one-way compatibility break.\n completionReq._endpointType = \"chat\";\n completionReq._context = getContext(req);\n\n const streaming = interactionsReq.stream !== false; // default true\n const model = completionReq.model;\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\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: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictStatus = 503;\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n logger.error(strictNoMatchLogLine(req.method ?? \"POST\", urlPath, skippedBySequenceOrTurn));\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: strictStatus,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify(buildInteractionsErrorResponse(strictMessage, \"UNAVAILABLE\")),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"gemini-interactions\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: res.statusCode ?? 200,\n fixture: null,\n source: \"proxy\",\n },\n });\n return;\n }\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify(buildInteractionsErrorResponse(\"No fixture matched\", \"NOT_FOUND\")),\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: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n writeErrorResponse(\n res,\n status,\n JSON.stringify(\n buildInteractionsErrorResponse(response.error.message, response.error.type ?? \"ERROR\"),\n ),\n { retryAfter: response.retryAfter },\n );\n return;\n }\n\n const interactionId = nextInteractionId();\n\n // Content + tool calls response\n if (isContentWithToolCallsResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\n \"webSearches in fixture response are not supported for Gemini Interactions API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildInteractionsContentWithToolCallsResponse(\n response.content,\n response.toolCalls,\n model,\n interactionId,\n logger,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsContentWithToolCallsSSEEvents(\n response.content,\n response.toolCalls,\n interactionId,\n chunkSize,\n logger,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\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(\n \"webSearches in fixture response are not supported for Gemini Interactions API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildInteractionsTextResponse(response.content, model, interactionId, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsTextSSEEvents(\n response.content,\n interactionId,\n chunkSize,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\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(\n \"webSearches in fixture response are not supported for Gemini Interactions API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildInteractionsToolCallResponse(\n response.toolCalls,\n model,\n interactionId,\n logger,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsToolCallSSEEvents(\n response.toolCalls,\n interactionId,\n logger,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\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: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify(\n buildInteractionsErrorResponse(\"Fixture response did not match any known type\", \"INTERNAL\"),\n ),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"gemini-interactions.cjs","names":["generateToolCallId","resolveFixtureBlocks","calculateDelay","delay","flattenHeaders","getContext","getTestId","matchFixtureDiagnostic","applyChaos","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","proxyAndRecord","resolveResponse","isErrorResponse","isContentWithToolCallsResponse","extractOverrides","createInterruptionSignal","isTextResponse","isToolCallResponse"],"sources":["../src/gemini-interactions.ts"],"sourcesContent":["/**\n * Google Gemini Interactions API support.\n *\n * Translates incoming Interactions requests into the ChatCompletionRequest\n * format used by the fixture router, and converts fixture responses back\n * into the Gemini Interactions format — either a single JSON response or\n * an SSE stream with event_type-based framing.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n ChatCompletionRequest,\n ChatMessage,\n Fixture,\n FixtureBlock,\n HandlerDefaults,\n RecordedTimings,\n ResponseOverrides,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n extractOverrides,\n generateToolCallId,\n flattenHeaders,\n getTestId,\n getContext,\n resolveResponse,\n resolveStrictMode,\n strictOverrideField,\n strictNoMatchMessage,\n strictNoMatchLogLine,\n resolveFixtureBlocks,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } 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// ─── Interactions request types ────────────────────────────────────────────\n\ninterface InteractionsContentBlock {\n type: string;\n text?: string;\n name?: string;\n call_id?: string;\n id?: string;\n arguments?: Record<string, unknown>;\n output?: unknown;\n result?: unknown;\n}\n\ninterface InteractionsTurn {\n role: string;\n content?: InteractionsContentBlock[];\n parts?: InteractionsContentBlock[];\n}\n\n/**\n * Top-level Step envelope accepted by the live Gemini Interactions API.\n * The SDK's TypeScript union does not include Step[], but the wire contract\n * does — clients following the live API send these at the top level of `input`.\n * Discriminated by `type`; no `role` field (distinguishes from Turn[]).\n */\ninterface InteractionsStep {\n type: string;\n content?: InteractionsContentBlock[];\n call_id?: string;\n id?: string;\n name?: string;\n result?: unknown;\n output?: unknown;\n is_error?: boolean;\n signature?: string;\n}\n\n/** Step types whose payload is a tool/agent result keyed by call_id. */\nconst STEP_RESULT_TYPES = new Set<string>([\n \"function_result\",\n \"code_execution_result\",\n \"url_context_result\",\n \"google_search_result\",\n \"google_maps_result\",\n \"mcp_server_tool_result\",\n \"file_search_result\",\n]);\n\n/** All recognized top-level Step types (used as the Step[] discriminator). */\nconst STEP_TYPES = new Set<string>([\"user_input\", \"model_output\", ...STEP_RESULT_TYPES]);\n\ninterface InteractionsFunctionTool {\n type: \"function\";\n name: string;\n description?: string;\n parameters?: object;\n}\n\ninterface InteractionsRequest {\n model?: string;\n input?: string | InteractionsTurn[] | InteractionsStep[] | InteractionsContentBlock[];\n system_instruction?: string;\n tools?: InteractionsFunctionTool[];\n generation_config?: {\n temperature?: number;\n max_output_tokens?: number;\n [key: string]: unknown;\n };\n stream?: boolean;\n previous_interaction_id?: string;\n [key: string]: unknown;\n}\n\n// ─── Input conversion: Interactions → ChatCompletionRequest ───────────────\n\nexport function geminiInteractionsToCompletionRequest(\n req: InteractionsRequest,\n): ChatCompletionRequest {\n const messages: ChatMessage[] = [];\n const model = req.model ?? \"gemini-2.5-flash\";\n\n // system_instruction → system message\n if (req.system_instruction) {\n messages.push({ role: \"system\", content: req.system_instruction });\n }\n\n // Parse input\n if (req.input !== undefined) {\n if (typeof req.input === \"string\") {\n // Simple string input → single user message\n messages.push({ role: \"user\", content: req.input });\n } else if (Array.isArray(req.input)) {\n // Could be Turn[], Step[], or Content[]\n const firstItem = req.input[0] as\n | InteractionsTurn\n | InteractionsStep\n | InteractionsContentBlock\n | undefined;\n const isStepArray =\n !!firstItem &&\n !(\"role\" in firstItem) &&\n typeof firstItem.type === \"string\" &&\n STEP_TYPES.has(firstItem.type);\n\n if (firstItem && \"role\" in firstItem) {\n // Turn[] format\n for (const turn of req.input as InteractionsTurn[]) {\n const role = turn.role === \"model\" ? \"assistant\" : turn.role;\n const blocks = turn.content ?? turn.parts;\n if (!blocks || blocks.length === 0) {\n if (role === \"user\" || role === \"assistant\") {\n messages.push({ role: role as \"user\" | \"assistant\", content: \"\" });\n }\n continue;\n }\n\n // Check for function_call or function_result parts\n const funcCallParts = blocks.filter((p) => p.type === \"function_call\");\n const funcResultParts = blocks.filter((p) => p.type === \"function_result\");\n const textParts = blocks.filter((p) => p.type === \"text\");\n\n if (funcCallParts.length > 0) {\n // Assistant tool call message\n const textContent = textParts.map((p) => p.text ?? \"\").join(\"\");\n messages.push({\n role: \"assistant\",\n content: textContent || null,\n tool_calls: funcCallParts.map((p) => ({\n id: p.id ?? p.call_id ?? generateToolCallId(),\n type: \"function\" as const,\n function: {\n name: p.name ?? \"\",\n arguments: JSON.stringify(p.arguments ?? {}),\n },\n })),\n });\n } else if (funcResultParts.length > 0) {\n // Tool response messages\n for (const part of funcResultParts) {\n const resultValue = part.result ?? part.output;\n messages.push({\n role: \"tool\",\n content:\n typeof resultValue === \"string\" ? resultValue : JSON.stringify(resultValue ?? \"\"),\n tool_call_id: part.call_id ?? part.id ?? \"\",\n });\n }\n // Any text parts alongside → separate user message\n if (textParts.length > 0) {\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n if (text) {\n messages.push({ role: \"user\", content: text });\n }\n }\n } else {\n // Text-only turn\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n if (role === \"user\" || role === \"assistant\" || role === \"system\") {\n messages.push({\n role: role as \"user\" | \"assistant\" | \"system\",\n content: text,\n });\n }\n }\n }\n } else if (isStepArray) {\n // Step[] format — the wire contract Google's /v1beta/interactions accepts.\n for (const step of req.input as InteractionsStep[]) {\n if (step.type === \"user_input\") {\n const text = (step.content ?? [])\n .filter((p) => p.type === \"text\")\n .map((p) => p.text ?? \"\")\n .join(\"\");\n messages.push({ role: \"user\", content: text });\n } else if (step.type === \"model_output\") {\n const blocks = step.content ?? [];\n const funcCallParts = blocks.filter((p) => p.type === \"function_call\");\n const textParts = blocks.filter((p) => p.type === \"text\");\n const textContent = textParts.map((p) => p.text ?? \"\").join(\"\");\n\n if (funcCallParts.length > 0) {\n messages.push({\n role: \"assistant\",\n content: textContent || null,\n tool_calls: funcCallParts.map((p) => ({\n id: p.id ?? p.call_id ?? generateToolCallId(),\n type: \"function\" as const,\n function: {\n name: p.name ?? \"\",\n arguments: JSON.stringify(p.arguments ?? {}),\n },\n })),\n });\n } else {\n messages.push({ role: \"assistant\", content: textContent });\n }\n } else if (STEP_RESULT_TYPES.has(step.type)) {\n const resultValue = step.result ?? step.output;\n messages.push({\n role: \"tool\",\n content:\n typeof resultValue === \"string\" ? resultValue : JSON.stringify(resultValue ?? \"\"),\n tool_call_id: step.call_id ?? step.id ?? \"\",\n });\n }\n }\n } else {\n // Content[] format — single user message with content blocks\n const textParts = (req.input as InteractionsContentBlock[]).filter(\n (p) => p.type === \"text\",\n );\n const text = textParts.map((p) => p.text ?? \"\").join(\"\");\n messages.push({ role: \"user\", content: text || \"\" });\n }\n }\n }\n\n // Convert tools\n let tools: ToolDefinition[] | undefined;\n if (req.tools && req.tools.length > 0) {\n const funcTools = req.tools.filter((t) => t.type === \"function\");\n if (funcTools.length > 0) {\n tools = funcTools.map((t) => ({\n type: \"function\" as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.parameters,\n },\n }));\n }\n }\n\n return {\n model,\n messages,\n stream: req.stream !== false, // default true\n temperature: req.generation_config?.temperature,\n max_tokens: req.generation_config?.max_output_tokens,\n tools,\n };\n}\n\n// ─── Interaction ID generation ────────────────────────────────────────────\n\nlet interactionCounter = 0;\n\nexport function resetInteractionCounter(): void {\n interactionCounter = 0;\n}\n\nfunction nextInteractionId(): string {\n return `aimock-int-${interactionCounter++}`;\n}\n\n// ─── Usage helpers ────────────────────────────────────────────────────────\n\nfunction interactionsUsage(overrides?: ResponseOverrides): {\n total_input_tokens: number;\n total_output_tokens: number;\n total_tokens: number;\n} {\n if (!overrides?.usage) return { total_input_tokens: 0, total_output_tokens: 0, total_tokens: 0 };\n const input =\n overrides.usage.input_tokens ??\n overrides.usage.prompt_tokens ??\n overrides.usage.promptTokenCount ??\n 0;\n const output =\n overrides.usage.output_tokens ??\n overrides.usage.completion_tokens ??\n overrides.usage.candidatesTokenCount ??\n 0;\n const total = overrides.usage.total_tokens ?? overrides.usage.totalTokenCount ?? input + output;\n return {\n total_input_tokens: input,\n total_output_tokens: output,\n total_tokens: total,\n };\n}\n\n// ─── Response building: fixture → Interactions format ─────────────────────\n\nexport function buildInteractionsTextResponse(\n content: string,\n model: string,\n interactionId: string,\n overrides?: ResponseOverrides,\n): object {\n return {\n id: interactionId,\n status: \"completed\",\n model: overrides?.model ?? model,\n role: \"model\",\n output_text: content,\n steps: [{ type: \"model_output\", content: [{ type: \"text\", text: content }] }],\n usage: interactionsUsage(overrides),\n };\n}\n\n// Build a single SDK 2.x function_call step from a fixture tool call,\n// reusing the existing malformed-arguments guard (logger.warn + {} fallback).\nfunction buildFunctionCallStep(tc: ToolCall, logger: Logger): object {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(`Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`);\n argsObj = {};\n }\n return {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: argsObj,\n };\n}\n\nexport function buildInteractionsToolCallResponse(\n toolCalls: ToolCall[],\n model: string,\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): object {\n return {\n id: interactionId,\n status: \"requires_action\",\n model: overrides?.model ?? model,\n role: \"model\",\n steps: toolCalls.map((tc) => buildFunctionCallStep(tc, logger)),\n usage: interactionsUsage(overrides),\n };\n}\n\nexport function buildInteractionsContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n blocks?: FixtureBlock[],\n): object {\n const steps: object[] = [];\n // Collect output_text in step order so the top-level field mirrors the\n // concatenated text steps regardless of where they appear in `steps`.\n let outputText = \"\";\n\n if (blocks && blocks.length > 0) {\n // NEW PATH: the non-stream `steps[]` array is index/step-addressed and\n // ordered, so emit one step per block in fixture ARRAY ORDER. A toolCall\n // block placed before a text block therefore yields a function_call step\n // ahead of the model_output step — tool-first, the opposite of the legacy\n // (text-step-always-first) shape below.\n const ordered = resolveFixtureBlocks(blocks);\n for (const block of ordered) {\n if (block.type === \"text\") {\n steps.push({ type: \"model_output\", content: [{ type: \"text\", text: block.text }] });\n outputText += block.text;\n } else {\n steps.push(\n buildFunctionCallStep(\n { name: block.name, arguments: block.arguments, id: block.id },\n logger,\n ),\n );\n }\n }\n } else {\n // LEGACY PATH: a single text step first, then function_call steps —\n // unchanged from the pre-blocks behavior (text always leads `steps`).\n steps.push({ type: \"model_output\", content: [{ type: \"text\", text: content }] });\n outputText = content;\n for (const tc of toolCalls) {\n steps.push(buildFunctionCallStep(tc, logger));\n }\n }\n\n return {\n id: interactionId,\n status: \"requires_action\",\n model: overrides?.model ?? model,\n role: \"model\",\n output_text: outputText,\n steps,\n usage: interactionsUsage(overrides),\n };\n}\n\nfunction buildInteractionsErrorResponse(message: string, code?: string): object {\n return {\n error: {\n code: code ?? \"INVALID_ARGUMENT\",\n message,\n },\n };\n}\n\n// ─── SSE event builders ──────────────────────────────────────────────────\n\ninterface InteractionsSSEEvent {\n event_type: string;\n [key: string]: unknown;\n}\n\nlet eventIdCounter = 0;\n\nexport function resetEventIdCounter(): void {\n eventIdCounter = 0;\n}\n\nfunction nextEventId(): string {\n return `evt_${++eventIdCounter}`;\n}\n\nexport function buildInteractionsTextSSEEvents(\n content: string,\n interactionId: string,\n chunkSize: number,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.created\n events.push({\n event_type: \"interaction.created\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // step.start — text step is the model_output\n events.push({\n event_type: \"step.start\",\n index: 0,\n step: { type: \"model_output\" },\n event_id: nextEventId(),\n });\n\n // step.delta(s) — inner delta shape is unchanged ({ type: \"text\", text })\n if (content.length === 0) {\n events.push({\n event_type: \"step.delta\",\n index: 0,\n delta: { type: \"text\", text: \"\" },\n event_id: nextEventId(),\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n event_type: \"step.delta\",\n index: 0,\n delta: { type: \"text\", text: slice },\n event_id: nextEventId(),\n });\n }\n }\n\n // step.stop\n events.push({\n event_type: \"step.stop\",\n index: 0,\n event_id: nextEventId(),\n });\n\n // interaction.completed\n events.push({\n event_type: \"interaction.completed\",\n interaction: {\n id: interactionId,\n status: \"completed\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\nexport function buildInteractionsToolCallSSEEvents(\n toolCalls: ToolCall[],\n interactionId: string,\n logger: Logger,\n overrides?: ResponseOverrides,\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.created\n events.push({\n event_type: \"interaction.created\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n // Each tool call gets its own step.start/delta/stop bracket. In SDK 2.x the\n // call identity (id, name) lives on step.start; the arguments stream as a\n // dedicated `arguments_delta` carrying a JSON-string fragment, and step.start\n // carries an empty `arguments: {}` placeholder.\n for (let idx = 0; idx < toolCalls.length; idx++) {\n const tc = toolCalls[idx];\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(\n `Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`,\n );\n argsObj = {};\n }\n\n events.push({\n event_type: \"step.start\",\n index: idx,\n step: {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: {},\n },\n event_id: nextEventId(),\n });\n\n // arguments_delta.arguments is a string fragment. The real SDK may split\n // the args across several fragments that concatenate into valid JSON; the\n // mock emits the whole serialized object as one fragment (a valid\n // degenerate case the collapser handles identically).\n events.push({\n event_type: \"step.delta\",\n index: idx,\n delta: {\n type: \"arguments_delta\",\n arguments: JSON.stringify(argsObj),\n },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"step.stop\",\n index: idx,\n event_id: nextEventId(),\n });\n }\n\n // interaction.completed\n events.push({\n event_type: \"interaction.completed\",\n interaction: {\n id: interactionId,\n status: \"requires_action\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\n// Emit the step.start/delta(s)/stop bracket for a text (model_output) step at\n// a given step `index`. Inner delta shape ({ type: \"text\", text }) and the\n// empty-content single-empty-delta behavior are unchanged from the legacy path.\nfunction pushTextStepEvents(\n events: InteractionsSSEEvent[],\n index: number,\n content: string,\n chunkSize: number,\n): void {\n events.push({\n event_type: \"step.start\",\n index,\n step: { type: \"model_output\" },\n event_id: nextEventId(),\n });\n\n if (content.length === 0) {\n events.push({\n event_type: \"step.delta\",\n index,\n delta: { type: \"text\", text: \"\" },\n event_id: nextEventId(),\n });\n } else {\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n events.push({\n event_type: \"step.delta\",\n index,\n delta: { type: \"text\", text: slice },\n event_id: nextEventId(),\n });\n }\n }\n\n events.push({\n event_type: \"step.stop\",\n index,\n event_id: nextEventId(),\n });\n}\n\n// Emit the step.start/arguments_delta/stop bracket for a function_call step at\n// a given step `index`. Identity (id, name) lives on step.start with an empty\n// `arguments: {}` placeholder; the arguments stream as a single\n// `arguments_delta` JSON-string fragment — unchanged from the legacy path.\nfunction pushFunctionCallStepEvents(\n events: InteractionsSSEEvent[],\n index: number,\n tc: ToolCall,\n logger: Logger,\n): void {\n let argsObj: unknown;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\");\n } catch {\n logger.warn(`Malformed JSON in fixture tool call arguments for \"${tc.name}\": ${tc.arguments}`);\n argsObj = {};\n }\n\n events.push({\n event_type: \"step.start\",\n index,\n step: {\n type: \"function_call\",\n id: tc.id || generateToolCallId(),\n name: tc.name,\n arguments: {},\n },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"step.delta\",\n index,\n delta: {\n type: \"arguments_delta\",\n arguments: JSON.stringify(argsObj),\n },\n event_id: nextEventId(),\n });\n\n events.push({\n event_type: \"step.stop\",\n index,\n event_id: nextEventId(),\n });\n}\n\nexport function buildInteractionsContentWithToolCallsSSEEvents(\n content: string,\n toolCalls: ToolCall[],\n interactionId: string,\n chunkSize: number,\n logger: Logger,\n overrides?: ResponseOverrides,\n blocks?: FixtureBlock[],\n): InteractionsSSEEvent[] {\n const events: InteractionsSSEEvent[] = [];\n\n // interaction.created\n events.push({\n event_type: \"interaction.created\",\n interaction: { id: interactionId, status: \"in_progress\" },\n event_id: nextEventId(),\n });\n\n if (blocks && blocks.length > 0) {\n // NEW PATH: stream one step per block in fixture ARRAY ORDER. The step\n // `index` increments with array position, so a toolCall block before a text\n // block yields a function_call step at a LOWER index than the model_output\n // step — tool-first, the opposite of the legacy (text-at-index-0) shape.\n const ordered = resolveFixtureBlocks(blocks);\n let idx = 0;\n for (const block of ordered) {\n if (block.type === \"text\") {\n pushTextStepEvents(events, idx, block.text, chunkSize);\n } else {\n pushFunctionCallStepEvents(\n events,\n idx,\n { name: block.name, arguments: block.arguments, id: block.id },\n logger,\n );\n }\n idx += 1;\n }\n } else {\n // LEGACY PATH: text content at index 0 (model_output step), tool calls at\n // index 1+ — byte-for-byte unchanged from the pre-blocks behavior.\n pushTextStepEvents(events, 0, content, chunkSize);\n for (let i = 0; i < toolCalls.length; i++) {\n pushFunctionCallStepEvents(events, i + 1, toolCalls[i], logger);\n }\n }\n\n // interaction.completed\n events.push({\n event_type: \"interaction.completed\",\n interaction: {\n id: interactionId,\n status: \"requires_action\",\n usage: interactionsUsage(overrides),\n },\n event_id: nextEventId(),\n });\n\n return events;\n}\n\n// ─── SSE writer for Interactions streaming ────────────────────────────────\n\ninterface InteractionsStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\n recordedTimings?: RecordedTimings;\n replaySpeed?: number;\n signal?: AbortSignal;\n onChunkSent?: () => void;\n}\n\nexport async function writeGeminiInteractionsSSEStream(\n res: http.ServerResponse,\n events: InteractionsSSEEvent[],\n optionsOrLatency?: number | InteractionsStreamOptions,\n): Promise<boolean> {\n const opts: InteractionsStreamOptions =\n typeof optionsOrLatency === \"number\" ? { latency: optionsOrLatency } : (optionsOrLatency ?? {});\n const latency = opts.latency ?? 0;\n const profile = opts.streamingProfile;\n const { recordedTimings, replaySpeed } = opts;\n const signal = opts.signal;\n const onChunkSent = opts.onChunkSent;\n\n if (res.writableEnded) return true;\n res.setHeader(\"Content-Type\", \"text/event-stream\");\n res.setHeader(\"Cache-Control\", \"no-cache\");\n res.setHeader(\"Connection\", \"keep-alive\");\n\n let chunkIndex = 0;\n for (const event of events) {\n const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed);\n if (chunkDelay > 0) await delay(chunkDelay, signal);\n if (signal?.aborted) return false;\n if (res.writableEnded) return true;\n // Data-only SSE (no event: prefix, no [DONE])\n res.write(`data: ${JSON.stringify(event)}\\n\\n`);\n // Only count step deltas for truncateAfterChunks — framing events\n // (interaction.created, step.start, step.stop, interaction.completed)\n // should not consume chunk budget or trigger the chunk-sent callback.\n if (event.event_type === \"step.delta\") {\n onChunkSent?.();\n chunkIndex++;\n }\n if (signal?.aborted) return false;\n }\n\n if (!res.writableEnded) {\n res.end();\n }\n return true;\n}\n\n// ─── Request handler ──────────────────────────────────────────────────────\n\nexport async function handleGeminiInteractions(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: Fixture[],\n journal: Journal,\n defaults: HandlerDefaults,\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n const urlPath = req.url ?? \"/v1beta/interactions\";\n\n let interactionsReq: InteractionsRequest;\n try {\n interactionsReq = JSON.parse(raw) as InteractionsRequest;\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: null,\n response: { status: 400, fixture: null },\n });\n writeErrorResponse(\n res,\n 400,\n JSON.stringify(\n buildInteractionsErrorResponse(`Malformed JSON body: ${detail}`, \"INVALID_ARGUMENT\"),\n ),\n );\n return;\n }\n\n // Convert to ChatCompletionRequest for fixture matching\n const completionReq = geminiInteractionsToCompletionRequest(interactionsReq);\n // Keep \"chat\" rather than \"gemini-interactions\" — the router's endpoint\n // compatibility filter (router.ts) treats \"chat\" as a pass-through that\n // matches any unendpointed fixture. Switching to \"gemini-interactions\"\n // would make the request fall into the multimedia guard branch, preventing\n // generic chat fixtures from matching and breaking existing users. The\n // recorder would also start emitting `endpoint: \"gemini-interactions\"` in\n // recorded fixtures, creating a one-way compatibility break.\n completionReq._endpointType = \"chat\";\n completionReq._context = getContext(req);\n\n const streaming = interactionsReq.stream !== false; // default true\n const model = completionReq.model;\n\n const testId = getTestId(req);\n const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\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: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n },\n fixture ? \"fixture\" : \"proxy\",\n defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n const effectiveStrict = resolveStrictMode(defaults.strict, req.headers);\n if (effectiveStrict) {\n const strictStatus = 503;\n const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n logger.error(strictNoMatchLogLine(req.method ?? \"POST\", urlPath, skippedBySequenceOrTurn));\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: strictStatus,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify(buildInteractionsErrorResponse(strictMessage, \"UNAVAILABLE\")),\n );\n return;\n }\n if (defaults.record) {\n const outcome = await proxyAndRecord(\n req,\n res,\n completionReq,\n \"gemini-interactions\",\n urlPath,\n fixtures,\n defaults,\n raw,\n );\n if (outcome === \"handled_by_hook\") return;\n if (outcome !== \"not_configured\") {\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: res.statusCode ?? 200,\n fixture: null,\n source: \"proxy\",\n },\n });\n return;\n }\n }\n journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: {\n status: 404,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n writeErrorResponse(\n res,\n 404,\n JSON.stringify(buildInteractionsErrorResponse(\"No fixture matched\", \"NOT_FOUND\")),\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: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status, fixture },\n });\n writeErrorResponse(\n res,\n status,\n JSON.stringify(\n buildInteractionsErrorResponse(response.error.message, response.error.type ?? \"ERROR\"),\n ),\n { retryAfter: response.retryAfter },\n );\n return;\n }\n\n const interactionId = nextInteractionId();\n\n // Content + tool calls response\n if (isContentWithToolCallsResponse(response)) {\n if (response.webSearches?.length) {\n logger.warn(\n \"webSearches in fixture response are not supported for Gemini Interactions API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildInteractionsContentWithToolCallsResponse(\n response.content ?? \"\",\n response.toolCalls ?? [],\n model,\n interactionId,\n logger,\n overrides,\n response.blocks,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsContentWithToolCallsSSEEvents(\n response.content ?? \"\",\n response.toolCalls ?? [],\n interactionId,\n chunkSize,\n logger,\n overrides,\n response.blocks,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\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(\n \"webSearches in fixture response are not supported for Gemini Interactions API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildInteractionsTextResponse(response.content, model, interactionId, overrides);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsTextSSEEvents(\n response.content,\n interactionId,\n chunkSize,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\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(\n \"webSearches in fixture response are not supported for Gemini Interactions API — ignoring\",\n );\n }\n const overrides = extractOverrides(response);\n const journalEntry = journal.add({\n method: req.method ?? \"POST\",\n path: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 200, fixture },\n });\n if (!streaming) {\n const body = buildInteractionsToolCallResponse(\n response.toolCalls,\n model,\n interactionId,\n logger,\n overrides,\n );\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const events = buildInteractionsToolCallSSEEvents(\n response.toolCalls,\n interactionId,\n logger,\n overrides,\n );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiInteractionsSSEStream(res, events, {\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: urlPath,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: 500, fixture },\n });\n writeErrorResponse(\n res,\n 500,\n JSON.stringify(\n buildInteractionsErrorResponse(\"Fixture response did not match any known type\", \"INTERNAL\"),\n ),\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"gemini-interactions.d.cts","names":[],"sources":["../src/gemini-interactions.ts"],"sourcesContent":[],"mappings":";;;;;;AAwEoC,UAzB1B,wBAAA,CAiDwB;EAOxB,IAAA,EAAA,MAAA;EAAmB,IAAA,CAAA,EAAA,MAAA;MAEV,CAAA,EAAA,MAAA;SAAqB,CAAA,EAAA,MAAA;KAAqB,EAAA,MAAA;WAEnD,CAAA,EAtDI,MAsDJ,CAAA,MAAA,EAAA,OAAA,CAAA;EAAwB,MAAA,CAAA,EAAA,OAAA;EAalB,MAAA,CAAA,EAAA,OAAA;;UA9DN,gBAAA,CA+DH;MACJ,EAAA,MAAA;EAAqB,OAAA,CAAA,EA9DZ,wBA8DY,EAAA;EAimBF,KAAA,CAAA,EA9pBZ,wBA8pBoC,EAAA;;;;;;;;UArpBpC,gBAAA,CA6pBP;EAAO,IAAA,EAAA,MAAA;YA3pBE;;;;;;;;;UAwBF,wBAAA;;;;;;UAOA,mBAAA;;mBAES,qBAAqB,qBAAqB;;UAEnD;;;;;;;;;;iBAaM,qCAAA,MACT,sBACJ;iBAimBmB,wBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B"}
1
+ {"version":3,"file":"gemini-interactions.d.cts","names":[],"sources":["../src/gemini-interactions.ts"],"sourcesContent":[],"mappings":";;;;;;AA0EoC,UAzB1B,wBAAA,CAiDwB;EAOxB,IAAA,EAAA,MAAA;EAAmB,IAAA,CAAA,EAAA,MAAA;MAEV,CAAA,EAAA,MAAA;SAAqB,CAAA,EAAA,MAAA;KAAqB,EAAA,MAAA;WAEnD,CAAA,EAtDI,MAsDJ,CAAA,MAAA,EAAA,OAAA,CAAA;EAAwB,MAAA,CAAA,EAAA,OAAA;EAalB,MAAA,CAAA,EAAA,OAAA;;UA9DN,gBAAA,CA+DH;MACJ,EAAA,MAAA;EAAqB,OAAA,CAAA,EA9DZ,wBA8DY,EAAA;EA8qBF,KAAA,CAAA,EA3uBZ,wBA2uBoC,EAAA;;;;;;;;UAluBpC,gBAAA,CA0uBP;EAAO,IAAA,EAAA,MAAA;YAxuBE;;;;;;;;;UAwBF,wBAAA;;;;;;UAOA,mBAAA;;mBAES,qBAAqB,qBAAqB;;UAEnD;;;;;;;;;;iBAaM,qCAAA,MACT,sBACJ;iBA8qBmB,wBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B"}
@@ -1 +1 @@
1
- {"version":3,"file":"gemini-interactions.d.ts","names":[],"sources":["../src/gemini-interactions.ts"],"sourcesContent":[],"mappings":";;;;;;AAwEoC,UAzB1B,wBAAA,CAiDwB;EAOxB,IAAA,EAAA,MAAA;EAAmB,IAAA,CAAA,EAAA,MAAA;MAEV,CAAA,EAAA,MAAA;SAAqB,CAAA,EAAA,MAAA;KAAqB,EAAA,MAAA;WAEnD,CAAA,EAtDI,MAsDJ,CAAA,MAAA,EAAA,OAAA,CAAA;EAAwB,MAAA,CAAA,EAAA,OAAA;EAalB,MAAA,CAAA,EAAA,OAAA;;UA9DN,gBAAA,CA+DH;MACJ,EAAA,MAAA;EAAqB,OAAA,CAAA,EA9DZ,wBA8DY,EAAA;EAimBF,KAAA,CAAA,EA9pBZ,wBA8pBoC,EAAA;;;;;;;;UArpBpC,gBAAA,CA6pBP;EAAO,IAAA,EAAA,MAAA;YA3pBE;;;;;;;;;UAwBF,wBAAA;;;;;;UAOA,mBAAA;;mBAES,qBAAqB,qBAAqB;;UAEnD;;;;;;;;;;iBAaM,qCAAA,MACT,sBACJ;iBAimBmB,wBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B"}
1
+ {"version":3,"file":"gemini-interactions.d.ts","names":[],"sources":["../src/gemini-interactions.ts"],"sourcesContent":[],"mappings":";;;;;;AA0EoC,UAzB1B,wBAAA,CAiDwB;EAOxB,IAAA,EAAA,MAAA;EAAmB,IAAA,CAAA,EAAA,MAAA;MAEV,CAAA,EAAA,MAAA;SAAqB,CAAA,EAAA,MAAA;KAAqB,EAAA,MAAA;WAEnD,CAAA,EAtDI,MAsDJ,CAAA,MAAA,EAAA,OAAA,CAAA;EAAwB,MAAA,CAAA,EAAA,OAAA;EAalB,MAAA,CAAA,EAAA,OAAA;;UA9DN,gBAAA,CA+DH;MACJ,EAAA,MAAA;EAAqB,OAAA,CAAA,EA9DZ,wBA8DY,EAAA;EA8qBF,KAAA,CAAA,EA3uBZ,wBA2uBoC,EAAA;;;;;;;;UAluBpC,gBAAA,CA0uBP;EAAO,IAAA,EAAA,MAAA;YAxuBE;;;;;;;;;UAwBF,wBAAA;;;;;;UAOA,mBAAA;;mBAES,qBAAqB,qBAAqB;;UAEnD;;;;;;;;;;iBAaM,qCAAA,MACT,sBACJ;iBA8qBmB,wBAAA,MACf,MAAA,CAAK,sBACL,MAAA,CAAK,uCAEA,oBACD,mBACC,uCACY,MAAA,CAAK,0BAC1B"}
@@ -1,4 +1,4 @@
1
- import { extractOverrides, flattenHeaders, generateToolCallId, getContext, getTestId, isContentWithToolCallsResponse, isErrorResponse, isTextResponse, isToolCallResponse, resolveResponse, resolveStrictMode, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
1
+ import { extractOverrides, flattenHeaders, generateToolCallId, getContext, getTestId, isContentWithToolCallsResponse, isErrorResponse, isTextResponse, isToolCallResponse, resolveFixtureBlocks, resolveResponse, resolveStrictMode, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
2
2
  import { matchFixtureDiagnostic } from "./router.js";
3
3
  import { calculateDelay, delay, writeErrorResponse } from "./sse-writer.js";
4
4
  import { createInterruptionSignal } from "./interruption.js";
@@ -180,63 +180,79 @@ function buildInteractionsTextResponse(content, model, interactionId, overrides)
180
180
  status: "completed",
181
181
  model: overrides?.model ?? model,
182
182
  role: "model",
183
- outputs: [{
184
- type: "text",
185
- text: content
183
+ output_text: content,
184
+ steps: [{
185
+ type: "model_output",
186
+ content: [{
187
+ type: "text",
188
+ text: content
189
+ }]
186
190
  }],
187
191
  usage: interactionsUsage(overrides)
188
192
  };
189
193
  }
194
+ function buildFunctionCallStep(tc, logger) {
195
+ let argsObj;
196
+ try {
197
+ argsObj = JSON.parse(tc.arguments || "{}");
198
+ } catch {
199
+ logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
200
+ argsObj = {};
201
+ }
202
+ return {
203
+ type: "function_call",
204
+ id: tc.id || generateToolCallId(),
205
+ name: tc.name,
206
+ arguments: argsObj
207
+ };
208
+ }
190
209
  function buildInteractionsToolCallResponse(toolCalls, model, interactionId, logger, overrides) {
191
210
  return {
192
211
  id: interactionId,
193
212
  status: "requires_action",
194
213
  model: overrides?.model ?? model,
195
214
  role: "model",
196
- outputs: toolCalls.map((tc) => {
197
- let argsObj;
198
- try {
199
- argsObj = JSON.parse(tc.arguments || "{}");
200
- } catch {
201
- logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
202
- argsObj = {};
203
- }
204
- return {
205
- type: "function_call",
206
- id: tc.id || generateToolCallId(),
207
- name: tc.name,
208
- arguments: argsObj
209
- };
210
- }),
215
+ steps: toolCalls.map((tc) => buildFunctionCallStep(tc, logger)),
211
216
  usage: interactionsUsage(overrides)
212
217
  };
213
218
  }
214
- function buildInteractionsContentWithToolCallsResponse(content, toolCalls, model, interactionId, logger, overrides) {
215
- const outputs = [{
216
- type: "text",
217
- text: content
218
- }];
219
- for (const tc of toolCalls) {
220
- let argsObj;
221
- try {
222
- argsObj = JSON.parse(tc.arguments || "{}");
223
- } catch {
224
- logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
225
- argsObj = {};
226
- }
227
- outputs.push({
228
- type: "function_call",
229
- id: tc.id || generateToolCallId(),
230
- name: tc.name,
231
- arguments: argsObj
219
+ function buildInteractionsContentWithToolCallsResponse(content, toolCalls, model, interactionId, logger, overrides, blocks) {
220
+ const steps = [];
221
+ let outputText = "";
222
+ if (blocks && blocks.length > 0) {
223
+ const ordered = resolveFixtureBlocks(blocks);
224
+ for (const block of ordered) if (block.type === "text") {
225
+ steps.push({
226
+ type: "model_output",
227
+ content: [{
228
+ type: "text",
229
+ text: block.text
230
+ }]
231
+ });
232
+ outputText += block.text;
233
+ } else steps.push(buildFunctionCallStep({
234
+ name: block.name,
235
+ arguments: block.arguments,
236
+ id: block.id
237
+ }, logger));
238
+ } else {
239
+ steps.push({
240
+ type: "model_output",
241
+ content: [{
242
+ type: "text",
243
+ text: content
244
+ }]
232
245
  });
246
+ outputText = content;
247
+ for (const tc of toolCalls) steps.push(buildFunctionCallStep(tc, logger));
233
248
  }
234
249
  return {
235
250
  id: interactionId,
236
251
  status: "requires_action",
237
252
  model: overrides?.model ?? model,
238
253
  role: "model",
239
- outputs,
254
+ output_text: outputText,
255
+ steps,
240
256
  usage: interactionsUsage(overrides)
241
257
  };
242
258
  }
@@ -256,7 +272,7 @@ function nextEventId() {
256
272
  function buildInteractionsTextSSEEvents(content, interactionId, chunkSize, overrides) {
257
273
  const events = [];
258
274
  events.push({
259
- event_type: "interaction.start",
275
+ event_type: "interaction.created",
260
276
  interaction: {
261
277
  id: interactionId,
262
278
  status: "in_progress"
@@ -264,13 +280,13 @@ function buildInteractionsTextSSEEvents(content, interactionId, chunkSize, overr
264
280
  event_id: nextEventId()
265
281
  });
266
282
  events.push({
267
- event_type: "content.start",
283
+ event_type: "step.start",
268
284
  index: 0,
269
- content: { type: "text" },
285
+ step: { type: "model_output" },
270
286
  event_id: nextEventId()
271
287
  });
272
288
  if (content.length === 0) events.push({
273
- event_type: "content.delta",
289
+ event_type: "step.delta",
274
290
  index: 0,
275
291
  delta: {
276
292
  type: "text",
@@ -281,7 +297,7 @@ function buildInteractionsTextSSEEvents(content, interactionId, chunkSize, overr
281
297
  else for (let i = 0; i < content.length; i += chunkSize) {
282
298
  const slice = content.slice(i, i + chunkSize);
283
299
  events.push({
284
- event_type: "content.delta",
300
+ event_type: "step.delta",
285
301
  index: 0,
286
302
  delta: {
287
303
  type: "text",
@@ -291,12 +307,12 @@ function buildInteractionsTextSSEEvents(content, interactionId, chunkSize, overr
291
307
  });
292
308
  }
293
309
  events.push({
294
- event_type: "content.stop",
310
+ event_type: "step.stop",
295
311
  index: 0,
296
312
  event_id: nextEventId()
297
313
  });
298
314
  events.push({
299
- event_type: "interaction.complete",
315
+ event_type: "interaction.completed",
300
316
  interaction: {
301
317
  id: interactionId,
302
318
  status: "completed",
@@ -309,7 +325,7 @@ function buildInteractionsTextSSEEvents(content, interactionId, chunkSize, overr
309
325
  function buildInteractionsToolCallSSEEvents(toolCalls, interactionId, logger, overrides) {
310
326
  const events = [];
311
327
  events.push({
312
- event_type: "interaction.start",
328
+ event_type: "interaction.created",
313
329
  interaction: {
314
330
  id: interactionId,
315
331
  status: "in_progress"
@@ -326,30 +342,33 @@ function buildInteractionsToolCallSSEEvents(toolCalls, interactionId, logger, ov
326
342
  argsObj = {};
327
343
  }
328
344
  events.push({
329
- event_type: "content.start",
345
+ event_type: "step.start",
330
346
  index: idx,
331
- content: { type: "function_call" },
347
+ step: {
348
+ type: "function_call",
349
+ id: tc.id || generateToolCallId(),
350
+ name: tc.name,
351
+ arguments: {}
352
+ },
332
353
  event_id: nextEventId()
333
354
  });
334
355
  events.push({
335
- event_type: "content.delta",
356
+ event_type: "step.delta",
336
357
  index: idx,
337
358
  delta: {
338
- type: "function_call",
339
- id: tc.id || generateToolCallId(),
340
- name: tc.name,
341
- arguments: argsObj
359
+ type: "arguments_delta",
360
+ arguments: JSON.stringify(argsObj)
342
361
  },
343
362
  event_id: nextEventId()
344
363
  });
345
364
  events.push({
346
- event_type: "content.stop",
365
+ event_type: "step.stop",
347
366
  index: idx,
348
367
  event_id: nextEventId()
349
368
  });
350
369
  }
351
370
  events.push({
352
- event_type: "interaction.complete",
371
+ event_type: "interaction.completed",
353
372
  interaction: {
354
373
  id: interactionId,
355
374
  status: "requires_action",
@@ -359,25 +378,16 @@ function buildInteractionsToolCallSSEEvents(toolCalls, interactionId, logger, ov
359
378
  });
360
379
  return events;
361
380
  }
362
- function buildInteractionsContentWithToolCallsSSEEvents(content, toolCalls, interactionId, chunkSize, logger, overrides) {
363
- const events = [];
381
+ function pushTextStepEvents(events, index, content, chunkSize) {
364
382
  events.push({
365
- event_type: "interaction.start",
366
- interaction: {
367
- id: interactionId,
368
- status: "in_progress"
369
- },
370
- event_id: nextEventId()
371
- });
372
- events.push({
373
- event_type: "content.start",
374
- index: 0,
375
- content: { type: "text" },
383
+ event_type: "step.start",
384
+ index,
385
+ step: { type: "model_output" },
376
386
  event_id: nextEventId()
377
387
  });
378
388
  if (content.length === 0) events.push({
379
- event_type: "content.delta",
380
- index: 0,
389
+ event_type: "step.delta",
390
+ index,
381
391
  delta: {
382
392
  type: "text",
383
393
  text: ""
@@ -387,8 +397,8 @@ function buildInteractionsContentWithToolCallsSSEEvents(content, toolCalls, inte
387
397
  else for (let i = 0; i < content.length; i += chunkSize) {
388
398
  const slice = content.slice(i, i + chunkSize);
389
399
  events.push({
390
- event_type: "content.delta",
391
- index: 0,
400
+ event_type: "step.delta",
401
+ index,
392
402
  delta: {
393
403
  type: "text",
394
404
  text: slice
@@ -397,45 +407,73 @@ function buildInteractionsContentWithToolCallsSSEEvents(content, toolCalls, inte
397
407
  });
398
408
  }
399
409
  events.push({
400
- event_type: "content.stop",
401
- index: 0,
410
+ event_type: "step.stop",
411
+ index,
402
412
  event_id: nextEventId()
403
413
  });
404
- for (let i = 0; i < toolCalls.length; i++) {
405
- const tc = toolCalls[i];
406
- const idx = i + 1;
407
- let argsObj;
408
- try {
409
- argsObj = JSON.parse(tc.arguments || "{}");
410
- } catch {
411
- logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
412
- argsObj = {};
414
+ }
415
+ function pushFunctionCallStepEvents(events, index, tc, logger) {
416
+ let argsObj;
417
+ try {
418
+ argsObj = JSON.parse(tc.arguments || "{}");
419
+ } catch {
420
+ logger.warn(`Malformed JSON in fixture tool call arguments for "${tc.name}": ${tc.arguments}`);
421
+ argsObj = {};
422
+ }
423
+ events.push({
424
+ event_type: "step.start",
425
+ index,
426
+ step: {
427
+ type: "function_call",
428
+ id: tc.id || generateToolCallId(),
429
+ name: tc.name,
430
+ arguments: {}
431
+ },
432
+ event_id: nextEventId()
433
+ });
434
+ events.push({
435
+ event_type: "step.delta",
436
+ index,
437
+ delta: {
438
+ type: "arguments_delta",
439
+ arguments: JSON.stringify(argsObj)
440
+ },
441
+ event_id: nextEventId()
442
+ });
443
+ events.push({
444
+ event_type: "step.stop",
445
+ index,
446
+ event_id: nextEventId()
447
+ });
448
+ }
449
+ function buildInteractionsContentWithToolCallsSSEEvents(content, toolCalls, interactionId, chunkSize, logger, overrides, blocks) {
450
+ const events = [];
451
+ events.push({
452
+ event_type: "interaction.created",
453
+ interaction: {
454
+ id: interactionId,
455
+ status: "in_progress"
456
+ },
457
+ event_id: nextEventId()
458
+ });
459
+ if (blocks && blocks.length > 0) {
460
+ const ordered = resolveFixtureBlocks(blocks);
461
+ let idx = 0;
462
+ for (const block of ordered) {
463
+ if (block.type === "text") pushTextStepEvents(events, idx, block.text, chunkSize);
464
+ else pushFunctionCallStepEvents(events, idx, {
465
+ name: block.name,
466
+ arguments: block.arguments,
467
+ id: block.id
468
+ }, logger);
469
+ idx += 1;
413
470
  }
414
- events.push({
415
- event_type: "content.start",
416
- index: idx,
417
- content: { type: "function_call" },
418
- event_id: nextEventId()
419
- });
420
- events.push({
421
- event_type: "content.delta",
422
- index: idx,
423
- delta: {
424
- type: "function_call",
425
- id: tc.id || generateToolCallId(),
426
- name: tc.name,
427
- arguments: argsObj
428
- },
429
- event_id: nextEventId()
430
- });
431
- events.push({
432
- event_type: "content.stop",
433
- index: idx,
434
- event_id: nextEventId()
435
- });
471
+ } else {
472
+ pushTextStepEvents(events, 0, content, chunkSize);
473
+ for (let i = 0; i < toolCalls.length; i++) pushFunctionCallStepEvents(events, i + 1, toolCalls[i], logger);
436
474
  }
437
475
  events.push({
438
- event_type: "interaction.complete",
476
+ event_type: "interaction.completed",
439
477
  interaction: {
440
478
  id: interactionId,
441
479
  status: "requires_action",
@@ -463,7 +501,7 @@ async function writeGeminiInteractionsSSEStream(res, events, optionsOrLatency) {
463
501
  if (signal?.aborted) return false;
464
502
  if (res.writableEnded) return true;
465
503
  res.write(`data: ${JSON.stringify(event)}\n\n`);
466
- if (event.event_type === "content.delta") {
504
+ if (event.event_type === "step.delta") {
467
505
  onChunkSent?.();
468
506
  chunkIndex++;
469
507
  }
@@ -593,11 +631,11 @@ async function handleGeminiInteractions(req, res, raw, fixtures, journal, defaul
593
631
  }
594
632
  });
595
633
  if (!streaming) {
596
- const body = buildInteractionsContentWithToolCallsResponse(response.content, response.toolCalls, model, interactionId, logger, overrides);
634
+ const body = buildInteractionsContentWithToolCallsResponse(response.content ?? "", response.toolCalls ?? [], model, interactionId, logger, overrides, response.blocks);
597
635
  res.writeHead(200, { "Content-Type": "application/json" });
598
636
  res.end(JSON.stringify(body));
599
637
  } else {
600
- const events = buildInteractionsContentWithToolCallsSSEEvents(response.content, response.toolCalls, interactionId, chunkSize, logger, overrides);
638
+ const events = buildInteractionsContentWithToolCallsSSEEvents(response.content ?? "", response.toolCalls ?? [], interactionId, chunkSize, logger, overrides, response.blocks);
601
639
  const interruption = createInterruptionSignal(fixture);
602
640
  if (!await writeGeminiInteractionsSSEStream(res, events, {
603
641
  latency,