@copilotkit/aimock 1.7.0 → 1.9.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 (134) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +3 -1
  4. package/dist/bedrock-converse.cjs +13 -8
  5. package/dist/bedrock-converse.cjs.map +1 -1
  6. package/dist/bedrock-converse.d.cts.map +1 -1
  7. package/dist/bedrock-converse.d.ts.map +1 -1
  8. package/dist/bedrock-converse.js +14 -9
  9. package/dist/bedrock-converse.js.map +1 -1
  10. package/dist/bedrock.cjs +51 -15
  11. package/dist/bedrock.cjs.map +1 -1
  12. package/dist/bedrock.d.cts.map +1 -1
  13. package/dist/bedrock.d.ts.map +1 -1
  14. package/dist/bedrock.js +52 -16
  15. package/dist/bedrock.js.map +1 -1
  16. package/dist/cohere.cjs +3 -2
  17. package/dist/cohere.cjs.map +1 -1
  18. package/dist/cohere.d.cts.map +1 -1
  19. package/dist/cohere.d.ts.map +1 -1
  20. package/dist/cohere.js +4 -3
  21. package/dist/cohere.js.map +1 -1
  22. package/dist/embeddings.cjs +3 -2
  23. package/dist/embeddings.cjs.map +1 -1
  24. package/dist/embeddings.d.cts.map +1 -1
  25. package/dist/embeddings.d.ts.map +1 -1
  26. package/dist/embeddings.js +4 -3
  27. package/dist/embeddings.js.map +1 -1
  28. package/dist/gemini.cjs +129 -35
  29. package/dist/gemini.cjs.map +1 -1
  30. package/dist/gemini.d.cts.map +1 -1
  31. package/dist/gemini.d.ts.map +1 -1
  32. package/dist/gemini.js +130 -36
  33. package/dist/gemini.js.map +1 -1
  34. package/dist/helpers.cjs +154 -3
  35. package/dist/helpers.cjs.map +1 -1
  36. package/dist/helpers.d.cts +1 -1
  37. package/dist/helpers.d.cts.map +1 -1
  38. package/dist/helpers.d.ts +1 -1
  39. package/dist/helpers.d.ts.map +1 -1
  40. package/dist/helpers.js +151 -4
  41. package/dist/helpers.js.map +1 -1
  42. package/dist/index.cjs +1 -0
  43. package/dist/index.d.cts +2 -2
  44. package/dist/index.d.ts +2 -2
  45. package/dist/index.js +2 -2
  46. package/dist/journal.cjs +26 -9
  47. package/dist/journal.cjs.map +1 -1
  48. package/dist/journal.d.cts +10 -5
  49. package/dist/journal.d.cts.map +1 -1
  50. package/dist/journal.d.ts +10 -5
  51. package/dist/journal.d.ts.map +1 -1
  52. package/dist/journal.js +26 -10
  53. package/dist/journal.js.map +1 -1
  54. package/dist/llmock.cjs +2 -2
  55. package/dist/llmock.cjs.map +1 -1
  56. package/dist/llmock.d.cts +1 -1
  57. package/dist/llmock.d.ts +1 -1
  58. package/dist/llmock.js +2 -2
  59. package/dist/llmock.js.map +1 -1
  60. package/dist/messages.cjs +192 -2
  61. package/dist/messages.cjs.map +1 -1
  62. package/dist/messages.d.cts.map +1 -1
  63. package/dist/messages.d.ts.map +1 -1
  64. package/dist/messages.js +193 -3
  65. package/dist/messages.js.map +1 -1
  66. package/dist/ollama.cjs +39 -13
  67. package/dist/ollama.cjs.map +1 -1
  68. package/dist/ollama.d.cts.map +1 -1
  69. package/dist/ollama.d.ts.map +1 -1
  70. package/dist/ollama.js +40 -14
  71. package/dist/ollama.js.map +1 -1
  72. package/dist/recorder.cjs +1 -1
  73. package/dist/recorder.cjs.map +1 -1
  74. package/dist/recorder.d.cts +1 -0
  75. package/dist/recorder.d.cts.map +1 -1
  76. package/dist/recorder.d.ts +1 -0
  77. package/dist/recorder.d.ts.map +1 -1
  78. package/dist/recorder.js +1 -1
  79. package/dist/recorder.js.map +1 -1
  80. package/dist/responses.cjs +250 -126
  81. package/dist/responses.cjs.map +1 -1
  82. package/dist/responses.d.cts.map +1 -1
  83. package/dist/responses.d.ts.map +1 -1
  84. package/dist/responses.js +251 -127
  85. package/dist/responses.js.map +1 -1
  86. package/dist/router.cjs +16 -10
  87. package/dist/router.cjs.map +1 -1
  88. package/dist/router.d.cts +1 -1
  89. package/dist/router.d.cts.map +1 -1
  90. package/dist/router.d.ts +1 -1
  91. package/dist/router.d.ts.map +1 -1
  92. package/dist/router.js +16 -10
  93. package/dist/router.js.map +1 -1
  94. package/dist/server.cjs +47 -7
  95. package/dist/server.cjs.map +1 -1
  96. package/dist/server.d.cts.map +1 -1
  97. package/dist/server.d.ts.map +1 -1
  98. package/dist/server.js +48 -8
  99. package/dist/server.js.map +1 -1
  100. package/dist/stream-collapse.cjs +48 -40
  101. package/dist/stream-collapse.cjs.map +1 -1
  102. package/dist/stream-collapse.d.cts.map +1 -1
  103. package/dist/stream-collapse.d.ts.map +1 -1
  104. package/dist/stream-collapse.js +48 -40
  105. package/dist/stream-collapse.js.map +1 -1
  106. package/dist/types.d.cts +22 -1
  107. package/dist/types.d.cts.map +1 -1
  108. package/dist/types.d.ts +22 -1
  109. package/dist/types.d.ts.map +1 -1
  110. package/dist/ws-gemini-live.cjs +4 -2
  111. package/dist/ws-gemini-live.cjs.map +1 -1
  112. package/dist/ws-gemini-live.d.cts +3 -1
  113. package/dist/ws-gemini-live.d.cts.map +1 -1
  114. package/dist/ws-gemini-live.d.ts +3 -1
  115. package/dist/ws-gemini-live.d.ts.map +1 -1
  116. package/dist/ws-gemini-live.js +4 -2
  117. package/dist/ws-gemini-live.js.map +1 -1
  118. package/dist/ws-realtime.cjs +4 -2
  119. package/dist/ws-realtime.cjs.map +1 -1
  120. package/dist/ws-realtime.d.cts +3 -1
  121. package/dist/ws-realtime.d.cts.map +1 -1
  122. package/dist/ws-realtime.d.ts +3 -1
  123. package/dist/ws-realtime.d.ts.map +1 -1
  124. package/dist/ws-realtime.js +4 -2
  125. package/dist/ws-realtime.js.map +1 -1
  126. package/dist/ws-responses.cjs +4 -2
  127. package/dist/ws-responses.cjs.map +1 -1
  128. package/dist/ws-responses.d.cts +3 -1
  129. package/dist/ws-responses.d.cts.map +1 -1
  130. package/dist/ws-responses.d.ts +3 -1
  131. package/dist/ws-responses.d.ts.map +1 -1
  132. package/dist/ws-responses.js +4 -2
  133. package/dist/ws-responses.js.map +1 -1
  134. package/package.json +5 -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 ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n RecordProviderKey,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isErrorResponse,\n generateToolCallId,\n flattenHeaders,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse, delay, calculateDelay } from \"./sse-writer.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Gemini request types ───────────────────────────────────────────────────\n\ninterface GeminiPart {\n text?: string;\n functionCall?: { name: string; args: Record<string, unknown>; id?: string };\n functionResponse?: { name: string; response: unknown };\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 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);\n\n if (funcResponses.length > 0) {\n // functionResponse → tool message\n for (let i = 0; i < funcResponses.length; i++) {\n const part = funcResponses[i];\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: `call_gemini_${part.functionResponse!.name}_${i}`,\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);\n\n if (funcCalls.length > 0) {\n messages.push({\n role: \"assistant\",\n content: null,\n tool_calls: funcCalls.map((p, i) => ({\n id: `call_gemini_${p.functionCall!.name}_${i}`,\n type: \"function\" as const,\n function: {\n name: p.functionCall!.name,\n arguments: JSON.stringify(p.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 }\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 tools,\n };\n}\n\n// ─── Response building: fixture → Gemini format ─────────────────────────────\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(content: string, chunkSize: number): GeminiResponseChunk[] {\n const chunks: GeminiResponseChunk[] = [];\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: \"STOP\" } : {}),\n },\n ],\n ...(isLast\n ? {\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n }\n : {}),\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: \"STOP\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n });\n }\n\n return chunks;\n}\n\nfunction buildGeminiToolCallStreamChunks(\n toolCalls: ToolCall[],\n logger: Logger,\n): GeminiResponseChunk[] {\n const parts: GeminiPart[] = toolCalls.map((tc) => {\n let argsObj: Record<string, unknown>;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\") as Record<string, unknown>;\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 functionCall: { name: tc.name, args: argsObj, id: tc.id || generateToolCallId() },\n };\n });\n\n // Gemini sends all tool calls in a single response chunk\n return [\n {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: \"FUNCTION_CALL\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n },\n ];\n}\n\n// Non-streaming response builders\n\nfunction buildGeminiTextResponse(content: string): GeminiResponseChunk {\n return {\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: content }] },\n finishReason: \"STOP\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n };\n}\n\nfunction buildGeminiToolCallResponse(toolCalls: ToolCall[], logger: Logger): GeminiResponseChunk {\n const parts: GeminiPart[] = toolCalls.map((tc) => {\n let argsObj: Record<string, unknown>;\n try {\n argsObj = JSON.parse(tc.arguments || \"{}\") as Record<string, unknown>;\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 functionCall: { name: tc.name, args: argsObj, id: tc.id || generateToolCallId() },\n };\n });\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: \"FUNCTION_CALL\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n };\n}\n\n// ─── SSE writer for Gemini streaming ────────────────────────────────────────\n\ninterface GeminiStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\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 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);\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 {\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\",\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\n const fixture = matchFixture(fixtures, completionReq, journal.fixtureMatchCounts);\n const path = req.url ?? `/v1beta/models/${model}:generateContent`;\n\n if (fixture) {\n journal.incrementFixtureMatchCount(fixture, fixtures);\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 defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const proxied = await proxyAndRecord(\n req,\n res,\n completionReq,\n providerKey,\n path,\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\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 },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${path}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n code: strictStatus,\n status: defaults.strict ? \"UNAVAILABLE\" : \"NOT_FOUND\",\n },\n }),\n );\n return;\n }\n\n const response = fixture.response;\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path,\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 return;\n }\n\n // Text response\n if (isTextResponse(response)) {\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);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiTextStreamChunks(response.content, chunkSize);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n 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);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiToolCallStreamChunks(response.toolCalls, logger);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path,\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 ChatCompletionRequest,\n ChatMessage,\n Fixture,\n HandlerDefaults,\n RecordProviderKey,\n StreamingProfile,\n ToolCall,\n ToolDefinition,\n} from \"./types.js\";\nimport {\n isTextResponse,\n isToolCallResponse,\n isContentWithToolCallsResponse,\n isErrorResponse,\n generateToolCallId,\n flattenHeaders,\n getTestId,\n} from \"./helpers.js\";\nimport { matchFixture } from \"./router.js\";\nimport { writeErrorResponse, delay, calculateDelay } from \"./sse-writer.js\";\nimport { createInterruptionSignal } from \"./interruption.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── 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 };\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 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);\n\n if (funcResponses.length > 0) {\n // functionResponse → tool message\n for (let i = 0; i < funcResponses.length; i++) {\n const part = funcResponses[i];\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: `call_gemini_${part.functionResponse!.name}_${i}`,\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);\n\n if (funcCalls.length > 0) {\n messages.push({\n role: \"assistant\",\n content: null,\n tool_calls: funcCalls.map((p, i) => ({\n id: `call_gemini_${p.functionCall!.name}_${i}`,\n type: \"function\" as const,\n function: {\n name: p.functionCall!.name,\n arguments: JSON.stringify(p.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 }\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 tools,\n };\n}\n\n// ─── Response building: fixture → Gemini format ─────────────────────────────\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): 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 // Content chunks (original logic unchanged)\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: \"STOP\" } : {}),\n },\n ],\n ...(isLast\n ? {\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n }\n : {}),\n };\n chunks.push(chunk);\n }\n\n // Handle empty content (original logic unchanged)\n if (content.length === 0) {\n chunks.push({\n candidates: [\n {\n content: { role: \"model\", parts: [{ text: \"\" }] },\n finishReason: \"STOP\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\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 return { functionCall: { name: tc.name, args: argsObj, id: tc.id || generateToolCallId() } };\n}\n\nfunction buildGeminiToolCallStreamChunks(\n toolCalls: ToolCall[],\n logger: Logger,\n): GeminiResponseChunk[] {\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n // Gemini sends all tool calls in a single response chunk\n return [\n {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: \"FUNCTION_CALL\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n },\n ];\n}\n\n// Non-streaming response builders\n\nfunction buildGeminiTextResponse(content: string, reasoning?: string): 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: \"STOP\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n };\n}\n\nfunction buildGeminiToolCallResponse(toolCalls: ToolCall[], logger: Logger): GeminiResponseChunk {\n const parts: GeminiPart[] = toolCalls.map((tc) => parseToolCallPart(tc, logger));\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: \"FUNCTION_CALL\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n };\n}\n\nfunction buildGeminiContentWithToolCallsStreamChunks(\n content: string,\n toolCalls: ToolCall[],\n chunkSize: number,\n logger: Logger,\n): GeminiResponseChunk[] {\n const chunks: GeminiResponseChunk[] = [];\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: \"FUNCTION_CALL\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n });\n\n return chunks;\n}\n\nfunction buildGeminiContentWithToolCallsResponse(\n content: string,\n toolCalls: ToolCall[],\n logger: Logger,\n): GeminiResponseChunk {\n const parts: GeminiPart[] = [\n { text: content },\n ...toolCalls.map((tc) => parseToolCallPart(tc, logger)),\n ];\n\n return {\n candidates: [\n {\n content: { role: \"model\", parts },\n finishReason: \"FUNCTION_CALL\",\n index: 0,\n },\n ],\n usageMetadata: {\n promptTokenCount: 0,\n candidatesTokenCount: 0,\n totalTokenCount: 0,\n },\n };\n}\n\n// ─── SSE writer for Gemini streaming ────────────────────────────────────────\n\ninterface GeminiStreamOptions {\n latency?: number;\n streamingProfile?: StreamingProfile;\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 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);\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 {\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\",\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\n const testId = getTestId(req);\n const fixture = matchFixture(\n fixtures,\n completionReq,\n journal.getFixtureMatchCountsForTest(testId),\n defaults.requestTransform,\n );\n const path = req.url ?? `/v1beta/models/${model}:generateContent`;\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 defaults.registry,\n defaults.logger,\n )\n )\n return;\n\n if (!fixture) {\n if (defaults.record) {\n const proxied = await proxyAndRecord(\n req,\n res,\n completionReq,\n providerKey,\n path,\n fixtures,\n defaults,\n raw,\n );\n if (proxied) {\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 },\n });\n return;\n }\n }\n const strictStatus = defaults.strict ? 503 : 404;\n const strictMessage = defaults.strict\n ? \"Strict mode: no fixture matched\"\n : \"No fixture matched\";\n if (defaults.strict) {\n logger.error(`STRICT: No fixture matched for ${req.method ?? \"POST\"} ${path}`);\n }\n journal.add({\n method: req.method ?? \"POST\",\n path,\n headers: flattenHeaders(req.headers),\n body: completionReq,\n response: { status: strictStatus, fixture: null },\n });\n writeErrorResponse(\n res,\n strictStatus,\n JSON.stringify({\n error: {\n message: strictMessage,\n code: strictStatus,\n status: defaults.strict ? \"UNAVAILABLE\" : \"NOT_FOUND\",\n },\n }),\n );\n return;\n }\n\n const response = fixture.response;\n const latency = fixture.latency ?? defaults.latency;\n const chunkSize = Math.max(1, fixture.chunkSize ?? defaults.chunkSize);\n\n // Error response\n if (isErrorResponse(response)) {\n const status = response.status ?? 500;\n journal.add({\n method: req.method ?? \"POST\",\n path,\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 return;\n }\n\n // Content + tool calls response (must be checked before isTextResponse / isToolCallResponse)\n if (isContentWithToolCallsResponse(response)) {\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 );\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 );\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Text response\n if (isTextResponse(response)) {\n 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, response.reasoning);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiTextStreamChunks(response.content, chunkSize, response.reasoning);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Tool call response\n if (isToolCallResponse(response)) {\n 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);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify(body));\n } else {\n const chunks = buildGeminiToolCallStreamChunks(response.toolCalls, logger);\n const interruption = createInterruptionSignal(fixture);\n const completed = await writeGeminiSSEStream(res, chunks, {\n latency,\n streamingProfile: fixture.streamingProfile,\n signal: interruption?.signal,\n onChunkSent: interruption?.tick,\n });\n if (!completed) {\n if (!res.writableEnded) res.destroy();\n journalEntry.response.interrupted = true;\n journalEntry.response.interruptReason = interruption?.reason();\n }\n interruption?.cleanup();\n }\n return;\n }\n\n // Unknown response type\n journal.add({\n method: req.method ?? \"POST\",\n path,\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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"}
package/dist/helpers.cjs CHANGED
@@ -34,16 +34,33 @@ function isTextResponse(r) {
34
34
  function isToolCallResponse(r) {
35
35
  return "toolCalls" in r && Array.isArray(r.toolCalls);
36
36
  }
37
+ function isContentWithToolCallsResponse(r) {
38
+ return "content" in r && typeof r.content === "string" && "toolCalls" in r && Array.isArray(r.toolCalls);
39
+ }
37
40
  function isErrorResponse(r) {
38
41
  return "error" in r && r.error !== null && typeof r.error === "object";
39
42
  }
40
43
  function isEmbeddingResponse(r) {
41
44
  return "embedding" in r && Array.isArray(r.embedding);
42
45
  }
43
- function buildTextChunks(content, model, chunkSize) {
46
+ function buildTextChunks(content, model, chunkSize, reasoning) {
44
47
  const id = generateId();
45
48
  const created = Math.floor(Date.now() / 1e3);
46
49
  const chunks = [];
50
+ if (reasoning) for (let i = 0; i < reasoning.length; i += chunkSize) {
51
+ const slice = reasoning.slice(i, i + chunkSize);
52
+ chunks.push({
53
+ id,
54
+ object: "chat.completion.chunk",
55
+ created,
56
+ model,
57
+ choices: [{
58
+ index: 0,
59
+ delta: { reasoning_content: slice },
60
+ finish_reason: null
61
+ }]
62
+ });
63
+ }
47
64
  chunks.push({
48
65
  id,
49
66
  object: "chat.completion.chunk",
@@ -157,7 +174,7 @@ function buildToolCallChunks(toolCalls, model, chunkSize) {
157
174
  });
158
175
  return chunks;
159
176
  }
160
- function buildTextCompletion(content, model) {
177
+ function buildTextCompletion(content, model, reasoning) {
161
178
  return {
162
179
  id: generateId(),
163
180
  object: "chat.completion",
@@ -168,7 +185,8 @@ function buildTextCompletion(content, model) {
168
185
  message: {
169
186
  role: "assistant",
170
187
  content,
171
- refusal: null
188
+ refusal: null,
189
+ ...reasoning ? { reasoning_content: reasoning } : {}
172
190
  },
173
191
  finish_reason: "stop"
174
192
  }],
@@ -209,6 +227,122 @@ function buildToolCallCompletion(toolCalls, model) {
209
227
  }
210
228
  };
211
229
  }
230
+ function buildContentWithToolCallsChunks(content, toolCalls, model, chunkSize) {
231
+ const id = generateId();
232
+ const created = Math.floor(Date.now() / 1e3);
233
+ const chunks = [];
234
+ chunks.push({
235
+ id,
236
+ object: "chat.completion.chunk",
237
+ created,
238
+ model,
239
+ choices: [{
240
+ index: 0,
241
+ delta: {
242
+ role: "assistant",
243
+ content: ""
244
+ },
245
+ finish_reason: null
246
+ }]
247
+ });
248
+ for (let i = 0; i < content.length; i += chunkSize) {
249
+ const slice = content.slice(i, i + chunkSize);
250
+ chunks.push({
251
+ id,
252
+ object: "chat.completion.chunk",
253
+ created,
254
+ model,
255
+ choices: [{
256
+ index: 0,
257
+ delta: { content: slice },
258
+ finish_reason: null
259
+ }]
260
+ });
261
+ }
262
+ for (let tcIdx = 0; tcIdx < toolCalls.length; tcIdx++) {
263
+ const tc = toolCalls[tcIdx];
264
+ const tcId = tc.id || generateToolCallId();
265
+ chunks.push({
266
+ id,
267
+ object: "chat.completion.chunk",
268
+ created,
269
+ model,
270
+ choices: [{
271
+ index: 0,
272
+ delta: { tool_calls: [{
273
+ index: tcIdx,
274
+ id: tcId,
275
+ type: "function",
276
+ function: {
277
+ name: tc.name,
278
+ arguments: ""
279
+ }
280
+ }] },
281
+ finish_reason: null
282
+ }]
283
+ });
284
+ const args = tc.arguments;
285
+ for (let i = 0; i < args.length; i += chunkSize) {
286
+ const slice = args.slice(i, i + chunkSize);
287
+ chunks.push({
288
+ id,
289
+ object: "chat.completion.chunk",
290
+ created,
291
+ model,
292
+ choices: [{
293
+ index: 0,
294
+ delta: { tool_calls: [{
295
+ index: tcIdx,
296
+ function: { arguments: slice }
297
+ }] },
298
+ finish_reason: null
299
+ }]
300
+ });
301
+ }
302
+ }
303
+ chunks.push({
304
+ id,
305
+ object: "chat.completion.chunk",
306
+ created,
307
+ model,
308
+ choices: [{
309
+ index: 0,
310
+ delta: {},
311
+ finish_reason: "tool_calls"
312
+ }]
313
+ });
314
+ return chunks;
315
+ }
316
+ function buildContentWithToolCallsCompletion(content, toolCalls, model) {
317
+ return {
318
+ id: generateId(),
319
+ object: "chat.completion",
320
+ created: Math.floor(Date.now() / 1e3),
321
+ model,
322
+ choices: [{
323
+ index: 0,
324
+ message: {
325
+ role: "assistant",
326
+ content,
327
+ refusal: null,
328
+ tool_calls: toolCalls.map((tc) => ({
329
+ id: tc.id || generateToolCallId(),
330
+ type: "function",
331
+ function: {
332
+ name: tc.name,
333
+ arguments: tc.arguments
334
+ }
335
+ }))
336
+ },
337
+ finish_reason: "tool_calls"
338
+ }],
339
+ usage: {
340
+ prompt_tokens: 0,
341
+ completion_tokens: 0,
342
+ total_tokens: 0
343
+ }
344
+ };
345
+ }
212
346
  function readBody(req) {
213
347
  return new Promise((resolve, reject) => {
214
348
  const chunks = [];
@@ -221,6 +355,19 @@ function matchesPattern(text, pattern) {
221
355
  if (typeof pattern === "string") return text.toLowerCase().includes(pattern.toLowerCase());
222
356
  return pattern.test(text);
223
357
  }
358
+ function getTestId(req) {
359
+ const headerValue = req.headers["x-test-id"];
360
+ if (Array.isArray(headerValue)) {
361
+ if (headerValue.length > 0 && headerValue[0]) return headerValue[0];
362
+ } else if (typeof headerValue === "string" && headerValue) return headerValue;
363
+ const url = req.url ?? "/";
364
+ const qIdx = url.indexOf("?");
365
+ if (qIdx !== -1) {
366
+ const queryValue = new URLSearchParams(url.slice(qIdx + 1)).get("testId");
367
+ if (queryValue) return queryValue;
368
+ }
369
+ return "__default__";
370
+ }
224
371
  const DEFAULT_EMBEDDING_DIMENSIONS = 1536;
225
372
  /**
226
373
  * Generate a deterministic embedding vector from input text.
@@ -256,6 +403,8 @@ function buildEmbeddingResponse(embeddings, model) {
256
403
  }
257
404
 
258
405
  //#endregion
406
+ exports.buildContentWithToolCallsChunks = buildContentWithToolCallsChunks;
407
+ exports.buildContentWithToolCallsCompletion = buildContentWithToolCallsCompletion;
259
408
  exports.buildEmbeddingResponse = buildEmbeddingResponse;
260
409
  exports.buildTextChunks = buildTextChunks;
261
410
  exports.buildTextCompletion = buildTextCompletion;
@@ -267,6 +416,8 @@ exports.generateId = generateId;
267
416
  exports.generateMessageId = generateMessageId;
268
417
  exports.generateToolCallId = generateToolCallId;
269
418
  exports.generateToolUseId = generateToolUseId;
419
+ exports.getTestId = getTestId;
420
+ exports.isContentWithToolCallsResponse = isContentWithToolCallsResponse;
270
421
  exports.isEmbeddingResponse = isEmbeddingResponse;
271
422
  exports.isErrorResponse = isErrorResponse;
272
423
  exports.isTextResponse = isTextResponse;
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.cjs","names":[],"sources":["../src/helpers.ts"],"sourcesContent":["import { createHash, randomBytes } from \"node:crypto\";\nimport type * as http from \"node:http\";\nimport type {\n FixtureResponse,\n TextResponse,\n ToolCallResponse,\n ErrorResponse,\n EmbeddingResponse,\n SSEChunk,\n ToolCall,\n ChatCompletion,\n} from \"./types.js\";\n\nconst REDACTED_HEADERS = new Set([\"authorization\", \"x-api-key\", \"api-key\"]);\n\nexport function flattenHeaders(headers: http.IncomingHttpHeaders): Record<string, string> {\n const flat: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue;\n if (REDACTED_HEADERS.has(key.toLowerCase())) {\n flat[key] = \"[REDACTED]\";\n } else {\n flat[key] = Array.isArray(value) ? value.join(\", \") : value;\n }\n }\n return flat;\n}\n\nexport function generateId(prefix = \"chatcmpl\"): string {\n return `${prefix}-${randomBytes(12).toString(\"base64url\")}`;\n}\n\nexport function generateToolCallId(): string {\n return `call_${randomBytes(12).toString(\"base64url\")}`;\n}\n\nexport function generateMessageId(): string {\n return `msg_${randomBytes(12).toString(\"base64url\")}`;\n}\n\nexport function generateToolUseId(): string {\n return `toolu_${randomBytes(12).toString(\"base64url\")}`;\n}\n\nexport function isTextResponse(r: FixtureResponse): r is TextResponse {\n return \"content\" in r && typeof (r as TextResponse).content === \"string\";\n}\n\nexport function isToolCallResponse(r: FixtureResponse): r is ToolCallResponse {\n return \"toolCalls\" in r && Array.isArray((r as ToolCallResponse).toolCalls);\n}\n\nexport function isErrorResponse(r: FixtureResponse): r is ErrorResponse {\n return (\n \"error\" in r &&\n (r as ErrorResponse).error !== null &&\n typeof (r as ErrorResponse).error === \"object\"\n );\n}\n\nexport function isEmbeddingResponse(r: FixtureResponse): r is EmbeddingResponse {\n return \"embedding\" in r && Array.isArray((r as EmbeddingResponse).embedding);\n}\n\nexport function buildTextChunks(content: string, model: string, chunkSize: number): SSEChunk[] {\n const id = generateId();\n const created = Math.floor(Date.now() / 1000);\n const chunks: SSEChunk[] = [];\n\n // Role chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: { role: \"assistant\", content: \"\" }, finish_reason: null }],\n });\n\n // Content chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: { content: slice }, finish_reason: null }],\n });\n }\n\n // Finish chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: {}, finish_reason: \"stop\" }],\n });\n\n return chunks;\n}\n\nexport function buildToolCallChunks(\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n): SSEChunk[] {\n const id = generateId();\n const created = Math.floor(Date.now() / 1000);\n const chunks: SSEChunk[] = [];\n\n // Role chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: { role: \"assistant\", content: null }, finish_reason: null }],\n });\n\n // Tool call chunks — one initial chunk per tool call, then argument chunks\n for (let tcIdx = 0; tcIdx < toolCalls.length; tcIdx++) {\n const tc = toolCalls[tcIdx];\n const tcId = tc.id || generateToolCallId();\n\n // Initial tool call chunk (id + function name)\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: tcIdx,\n id: tcId,\n type: \"function\",\n function: { name: tc.name, arguments: \"\" },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n });\n\n // Argument streaming chunks\n const args = tc.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n const slice = args.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [{ index: tcIdx, function: { arguments: slice } }],\n },\n finish_reason: null,\n },\n ],\n });\n }\n }\n\n // Finish chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: {}, finish_reason: \"tool_calls\" }],\n });\n\n return chunks;\n}\n\n// Non-streaming response builders\n\nexport function buildTextCompletion(content: string, model: string): ChatCompletion {\n return {\n id: generateId(),\n object: \"chat.completion\",\n created: Math.floor(Date.now() / 1000),\n model,\n choices: [\n {\n index: 0,\n message: { role: \"assistant\", content, refusal: null },\n finish_reason: \"stop\",\n },\n ],\n usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n };\n}\n\nexport function buildToolCallCompletion(toolCalls: ToolCall[], model: string): ChatCompletion {\n return {\n id: generateId(),\n object: \"chat.completion\",\n created: Math.floor(Date.now() / 1000),\n model,\n choices: [\n {\n index: 0,\n message: {\n role: \"assistant\",\n content: null,\n refusal: null,\n tool_calls: toolCalls.map((tc) => ({\n id: tc.id || generateToolCallId(),\n type: \"function\" as const,\n function: { name: tc.name, arguments: tc.arguments },\n })),\n },\n finish_reason: \"tool_calls\",\n },\n ],\n usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n };\n}\n\n// ─── HTTP helpers ─────────────────────────────────────────────────────────\n\nexport function readBody(req: http.IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString()));\n req.on(\"error\", reject);\n });\n}\n\n// ─── Pattern matching ─────────────────────────────────────────────────────\n\nexport function matchesPattern(text: string, pattern: string | RegExp): boolean {\n if (typeof pattern === \"string\") {\n return text.toLowerCase().includes(pattern.toLowerCase());\n }\n return pattern.test(text);\n}\n\n// ─── Embedding helpers ─────────────────────────────────────────────────────\n\nconst DEFAULT_EMBEDDING_DIMENSIONS = 1536;\n\n/**\n * Generate a deterministic embedding vector from input text.\n * Hashes the input with SHA-256 and spreads the hash bytes across\n * the requested number of dimensions, producing values in [-1, 1].\n */\nexport function generateDeterministicEmbedding(\n input: string,\n dimensions: number = DEFAULT_EMBEDDING_DIMENSIONS,\n): number[] {\n let currentHash = createHash(\"sha256\").update(input).digest();\n const embedding: number[] = new Array(dimensions);\n for (let i = 0; i < dimensions; i++) {\n if (i > 0 && i % 32 === 0) {\n currentHash = createHash(\"sha256\").update(currentHash).digest();\n }\n // Map 0-255 → -1.0 to 1.0\n embedding[i] = currentHash[i % 32] / 127.5 - 1;\n }\n return embedding;\n}\n\nexport interface EmbeddingAPIResponse {\n object: \"list\";\n data: { object: \"embedding\"; index: number; embedding: number[] }[];\n model: string;\n usage: { prompt_tokens: number; total_tokens: number };\n}\n\n/**\n * Build an OpenAI-format embeddings API response for one or more inputs.\n */\nexport function buildEmbeddingResponse(\n embeddings: number[][],\n model: string,\n): EmbeddingAPIResponse {\n return {\n object: \"list\",\n data: embeddings.map((embedding, index) => ({\n object: \"embedding\" as const,\n index,\n embedding,\n })),\n model,\n usage: { prompt_tokens: 0, total_tokens: 0 },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"helpers.cjs","names":[],"sources":["../src/helpers.ts"],"sourcesContent":["import { createHash, randomBytes } from \"node:crypto\";\nimport type * as http from \"node:http\";\nimport type {\n FixtureResponse,\n TextResponse,\n ToolCallResponse,\n ContentWithToolCallsResponse,\n ErrorResponse,\n EmbeddingResponse,\n SSEChunk,\n ToolCall,\n ChatCompletion,\n} from \"./types.js\";\n\nconst REDACTED_HEADERS = new Set([\"authorization\", \"x-api-key\", \"api-key\"]);\n\nexport function flattenHeaders(headers: http.IncomingHttpHeaders): Record<string, string> {\n const flat: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue;\n if (REDACTED_HEADERS.has(key.toLowerCase())) {\n flat[key] = \"[REDACTED]\";\n } else {\n flat[key] = Array.isArray(value) ? value.join(\", \") : value;\n }\n }\n return flat;\n}\n\nexport function generateId(prefix = \"chatcmpl\"): string {\n return `${prefix}-${randomBytes(12).toString(\"base64url\")}`;\n}\n\nexport function generateToolCallId(): string {\n return `call_${randomBytes(12).toString(\"base64url\")}`;\n}\n\nexport function generateMessageId(): string {\n return `msg_${randomBytes(12).toString(\"base64url\")}`;\n}\n\nexport function generateToolUseId(): string {\n return `toolu_${randomBytes(12).toString(\"base64url\")}`;\n}\n\nexport function isTextResponse(r: FixtureResponse): r is TextResponse {\n return \"content\" in r && typeof (r as TextResponse).content === \"string\";\n}\n\nexport function isToolCallResponse(r: FixtureResponse): r is ToolCallResponse {\n return \"toolCalls\" in r && Array.isArray((r as ToolCallResponse).toolCalls);\n}\n\nexport function isContentWithToolCallsResponse(\n r: FixtureResponse,\n): r is ContentWithToolCallsResponse {\n return (\n \"content\" in r &&\n typeof (r as ContentWithToolCallsResponse).content === \"string\" &&\n \"toolCalls\" in r &&\n Array.isArray((r as ContentWithToolCallsResponse).toolCalls)\n );\n}\n\nexport function isErrorResponse(r: FixtureResponse): r is ErrorResponse {\n return (\n \"error\" in r &&\n (r as ErrorResponse).error !== null &&\n typeof (r as ErrorResponse).error === \"object\"\n );\n}\n\nexport function isEmbeddingResponse(r: FixtureResponse): r is EmbeddingResponse {\n return \"embedding\" in r && Array.isArray((r as EmbeddingResponse).embedding);\n}\n\nexport function buildTextChunks(\n content: string,\n model: string,\n chunkSize: number,\n reasoning?: string,\n): SSEChunk[] {\n const id = generateId();\n const created = Math.floor(Date.now() / 1000);\n const chunks: SSEChunk[] = [];\n\n // Reasoning chunks (emitted before content, OpenRouter format)\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 id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: { reasoning_content: slice }, finish_reason: null }],\n });\n }\n }\n\n // Role chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: { role: \"assistant\", content: \"\" }, finish_reason: null }],\n });\n\n // Content chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: { content: slice }, finish_reason: null }],\n });\n }\n\n // Finish chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: {}, finish_reason: \"stop\" }],\n });\n\n return chunks;\n}\n\nexport function buildToolCallChunks(\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n): SSEChunk[] {\n const id = generateId();\n const created = Math.floor(Date.now() / 1000);\n const chunks: SSEChunk[] = [];\n\n // Role chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: { role: \"assistant\", content: null }, finish_reason: null }],\n });\n\n // Tool call chunks — one initial chunk per tool call, then argument chunks\n for (let tcIdx = 0; tcIdx < toolCalls.length; tcIdx++) {\n const tc = toolCalls[tcIdx];\n const tcId = tc.id || generateToolCallId();\n\n // Initial tool call chunk (id + function name)\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: tcIdx,\n id: tcId,\n type: \"function\",\n function: { name: tc.name, arguments: \"\" },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n });\n\n // Argument streaming chunks\n const args = tc.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n const slice = args.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [{ index: tcIdx, function: { arguments: slice } }],\n },\n finish_reason: null,\n },\n ],\n });\n }\n }\n\n // Finish chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: {}, finish_reason: \"tool_calls\" }],\n });\n\n return chunks;\n}\n\n// Non-streaming response builders\n\nexport function buildTextCompletion(\n content: string,\n model: string,\n reasoning?: string,\n): ChatCompletion {\n return {\n id: generateId(),\n object: \"chat.completion\",\n created: Math.floor(Date.now() / 1000),\n model,\n choices: [\n {\n index: 0,\n message: {\n role: \"assistant\",\n content,\n refusal: null,\n ...(reasoning ? { reasoning_content: reasoning } : {}),\n },\n finish_reason: \"stop\",\n },\n ],\n usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n };\n}\n\nexport function buildToolCallCompletion(toolCalls: ToolCall[], model: string): ChatCompletion {\n return {\n id: generateId(),\n object: \"chat.completion\",\n created: Math.floor(Date.now() / 1000),\n model,\n choices: [\n {\n index: 0,\n message: {\n role: \"assistant\",\n content: null,\n refusal: null,\n tool_calls: toolCalls.map((tc) => ({\n id: tc.id || generateToolCallId(),\n type: \"function\" as const,\n function: { name: tc.name, arguments: tc.arguments },\n })),\n },\n finish_reason: \"tool_calls\",\n },\n ],\n usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n };\n}\n\nexport function buildContentWithToolCallsChunks(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n chunkSize: number,\n): SSEChunk[] {\n const id = generateId();\n const created = Math.floor(Date.now() / 1000);\n const chunks: SSEChunk[] = [];\n\n // Role chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: { role: \"assistant\", content: \"\" }, finish_reason: null }],\n });\n\n // Content chunks\n for (let i = 0; i < content.length; i += chunkSize) {\n const slice = content.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: { content: slice }, finish_reason: null }],\n });\n }\n\n // Tool call chunks — one initial chunk per tool call, then argument chunks\n for (let tcIdx = 0; tcIdx < toolCalls.length; tcIdx++) {\n const tc = toolCalls[tcIdx];\n const tcId = tc.id || generateToolCallId();\n\n // Initial tool call chunk (id + function name)\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: tcIdx,\n id: tcId,\n type: \"function\",\n function: { name: tc.name, arguments: \"\" },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n });\n\n // Argument streaming chunks\n const args = tc.arguments;\n for (let i = 0; i < args.length; i += chunkSize) {\n const slice = args.slice(i, i + chunkSize);\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [{ index: tcIdx, function: { arguments: slice } }],\n },\n finish_reason: null,\n },\n ],\n });\n }\n }\n\n // Finish chunk\n chunks.push({\n id,\n object: \"chat.completion.chunk\",\n created,\n model,\n choices: [{ index: 0, delta: {}, finish_reason: \"tool_calls\" }],\n });\n\n return chunks;\n}\n\nexport function buildContentWithToolCallsCompletion(\n content: string,\n toolCalls: ToolCall[],\n model: string,\n): ChatCompletion {\n return {\n id: generateId(),\n object: \"chat.completion\",\n created: Math.floor(Date.now() / 1000),\n model,\n choices: [\n {\n index: 0,\n message: {\n role: \"assistant\",\n content,\n refusal: null,\n tool_calls: toolCalls.map((tc) => ({\n id: tc.id || generateToolCallId(),\n type: \"function\" as const,\n function: { name: tc.name, arguments: tc.arguments },\n })),\n },\n finish_reason: \"tool_calls\",\n },\n ],\n usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n };\n}\n\n// ─── HTTP helpers ─────────────────────────────────────────────────────────\n\nexport function readBody(req: http.IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => resolve(Buffer.concat(chunks).toString()));\n req.on(\"error\", reject);\n });\n}\n\n// ─── Pattern matching ─────────────────────────────────────────────────────\n\nexport function matchesPattern(text: string, pattern: string | RegExp): boolean {\n if (typeof pattern === \"string\") {\n return text.toLowerCase().includes(pattern.toLowerCase());\n }\n return pattern.test(text);\n}\n\nexport function getTestId(req: http.IncomingMessage): string {\n const headerValue = req.headers[\"x-test-id\"];\n if (Array.isArray(headerValue)) {\n if (headerValue.length > 0 && headerValue[0]) return headerValue[0];\n } else if (typeof headerValue === \"string\" && headerValue) {\n return headerValue;\n }\n\n const url = req.url ?? \"/\";\n const qIdx = url.indexOf(\"?\");\n if (qIdx !== -1) {\n const params = new URLSearchParams(url.slice(qIdx + 1));\n const queryValue = params.get(\"testId\");\n if (queryValue) return queryValue;\n }\n\n // Duplicated from journal.ts DEFAULT_TEST_ID — importing it here would create\n // a circular dependency (journal.ts imports from helpers.ts).\n return \"__default__\";\n}\n\n// ─── Embedding helpers ─────────────────────────────────────────────────────\n\nconst DEFAULT_EMBEDDING_DIMENSIONS = 1536;\n\n/**\n * Generate a deterministic embedding vector from input text.\n * Hashes the input with SHA-256 and spreads the hash bytes across\n * the requested number of dimensions, producing values in [-1, 1].\n */\nexport function generateDeterministicEmbedding(\n input: string,\n dimensions: number = DEFAULT_EMBEDDING_DIMENSIONS,\n): number[] {\n let currentHash = createHash(\"sha256\").update(input).digest();\n const embedding: number[] = new Array(dimensions);\n for (let i = 0; i < dimensions; i++) {\n if (i > 0 && i % 32 === 0) {\n currentHash = createHash(\"sha256\").update(currentHash).digest();\n }\n // Map 0-255 → -1.0 to 1.0\n embedding[i] = currentHash[i % 32] / 127.5 - 1;\n }\n return embedding;\n}\n\nexport interface EmbeddingAPIResponse {\n object: \"list\";\n data: { object: \"embedding\"; index: number; embedding: number[] }[];\n model: string;\n usage: { prompt_tokens: number; total_tokens: number };\n}\n\n/**\n * Build an OpenAI-format embeddings API response for one or more inputs.\n */\nexport function buildEmbeddingResponse(\n embeddings: number[][],\n model: string,\n): EmbeddingAPIResponse {\n return {\n object: \"list\",\n data: embeddings.map((embedding, index) => ({\n object: \"embedding\" as const,\n index,\n embedding,\n })),\n model,\n usage: { prompt_tokens: 0, total_tokens: 0 },\n };\n}\n"],"mappings":"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"}
@@ -8,7 +8,7 @@ declare function generateToolCallId(): string;
8
8
  declare function generateMessageId(): string;
9
9
  declare function generateToolUseId(): string;
10
10
  declare function isEmbeddingResponse(r: FixtureResponse): r is EmbeddingResponse;
11
- declare function buildTextChunks(content: string, model: string, chunkSize: number): SSEChunk[];
11
+ declare function buildTextChunks(content: string, model: string, chunkSize: number, reasoning?: string): SSEChunk[];
12
12
  declare function buildToolCallChunks(toolCalls: ToolCall[], model: string, chunkSize: number): SSEChunk[];
13
13
  /**
14
14
  * Generate a deterministic embedding vector from input text.
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.cts","names":[],"sources":["../src/helpers.ts"],"sourcesContent":[],"mappings":";;;;iBAegB,cAAA,UAAwB,IAAA,CAAK,sBAAsB;iBAanD,UAAA;AAbA,iBAiBA,kBAAA,CAAA,CAjBc,EAAA,MAAA;AAAA,iBAqBd,iBAAA,CAAA,CArBc,EAAA,MAAA;AAAU,iBAyBxB,iBAAA,CAAA,CAzB6B,EAAA,MAAA;AAiB7B,iBA4BA,mBAAA,CA5BkB,CAAA,EA4BK,eA5BL,CAAA,EAAA,CAAA,IA4B4B,iBA5B5B;AAIlB,iBA4BA,eAAA,CA5BiB,OAAA,EAAA,MAAA,EAAA,KAAA,EAAA,MAAA,EAAA,SAAA,EAAA,MAAA,CAAA,EA4BmD,QA5BnD,EAAA;AAIjB,iBA8DA,mBAAA,CA9DiB,SAAA,EA+DpB,QA/DoB,EAAA,EAAA,KAAA,EAAA,MAAA,EAAA,SAAA,EAAA,MAAA,CAAA,EAkE9B,QAlE8B,EAAA;;AAwBjC;AAsCA;;;AAIG,iBAuJa,8BAAA,CAvJb,KAAA,EAAA,MAAA,EAAA,UAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA,EAAA;AAAQ,UAuKM,oBAAA,CAvKN;EAuJK,MAAA,EAAA,MAAA;EAgBC,IAAA,EAAA;IAUD,MAAA,EAAA,WAAA;;;;;;;;;;;;;iBAAA,sBAAA,yCAGb"}
1
+ {"version":3,"file":"helpers.d.cts","names":[],"sources":["../src/helpers.ts"],"sourcesContent":[],"mappings":";;;;iBAgBgB,cAAA,UAAwB,IAAA,CAAK,sBAAsB;iBAanD,UAAA;AAbA,iBAiBA,kBAAA,CAAA,CAjBc,EAAA,MAAA;AAAA,iBAqBd,iBAAA,CAAA,CArBc,EAAA,MAAA;AAAU,iBAyBxB,iBAAA,CAAA,CAzB6B,EAAA,MAAA;AAqB7B,iBAmCA,mBAAA,CAnCiB,CAAA,EAmCM,eAnCN,CAAA,EAAA,CAAA,IAmC6B,iBAnC7B;AAIjB,iBAmCA,eAAA,CAnCiB,OAAA,EAAA,MAAA,EAAA,KAAA,EAAA,MAAA,EAAA,SAAA,EAAA,MAAA,EAAA,SAAA,CAAA,EAAA,MAAA,CAAA,EAwC9B,QAxC8B,EAAA;AA+BjB,iBA6DA,mBAAA,CA7DmB,SAAA,EA8DtB,QA9DsB,EAAA,EAAA,KAAA,EAAA,MAAA,EAAA,SAAA,EAAA,MAAA,CAAA,EAiEhC,QAjEgC,EAAA;;;;AAkXnC;AAgBA;AAUgB,iBA1BA,8BAAA,CA6Bb,KAAA,EAAoB,MAAA,EAAA,UAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA,EAAA;UAbN,oBAAA;;;;;;;;;;;;;;;;iBAUD,sBAAA,yCAGb"}
package/dist/helpers.d.ts CHANGED
@@ -8,7 +8,7 @@ declare function generateToolCallId(): string;
8
8
  declare function generateMessageId(): string;
9
9
  declare function generateToolUseId(): string;
10
10
  declare function isEmbeddingResponse(r: FixtureResponse): r is EmbeddingResponse;
11
- declare function buildTextChunks(content: string, model: string, chunkSize: number): SSEChunk[];
11
+ declare function buildTextChunks(content: string, model: string, chunkSize: number, reasoning?: string): SSEChunk[];
12
12
  declare function buildToolCallChunks(toolCalls: ToolCall[], model: string, chunkSize: number): SSEChunk[];
13
13
  /**
14
14
  * Generate a deterministic embedding vector from input text.
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","names":[],"sources":["../src/helpers.ts"],"sourcesContent":[],"mappings":";;;;iBAegB,cAAA,UAAwB,IAAA,CAAK,sBAAsB;iBAanD,UAAA;AAbA,iBAiBA,kBAAA,CAAA,CAjBc,EAAA,MAAA;AAAA,iBAqBd,iBAAA,CAAA,CArBc,EAAA,MAAA;AAAU,iBAyBxB,iBAAA,CAAA,CAzB6B,EAAA,MAAA;AAiB7B,iBA4BA,mBAAA,CA5BkB,CAAA,EA4BK,eA5BL,CAAA,EAAA,CAAA,IA4B4B,iBA5B5B;AAIlB,iBA4BA,eAAA,CA5BiB,OAAA,EAAA,MAAA,EAAA,KAAA,EAAA,MAAA,EAAA,SAAA,EAAA,MAAA,CAAA,EA4BmD,QA5BnD,EAAA;AAIjB,iBA8DA,mBAAA,CA9DiB,SAAA,EA+DpB,QA/DoB,EAAA,EAAA,KAAA,EAAA,MAAA,EAAA,SAAA,EAAA,MAAA,CAAA,EAkE9B,QAlE8B,EAAA;;AAwBjC;AAsCA;;;AAIG,iBAuJa,8BAAA,CAvJb,KAAA,EAAA,MAAA,EAAA,UAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA,EAAA;AAAQ,UAuKM,oBAAA,CAvKN;EAuJK,MAAA,EAAA,MAAA;EAgBC,IAAA,EAAA;IAUD,MAAA,EAAA,WAAA;;;;;;;;;;;;;iBAAA,sBAAA,yCAGb"}
1
+ {"version":3,"file":"helpers.d.ts","names":[],"sources":["../src/helpers.ts"],"sourcesContent":[],"mappings":";;;;iBAgBgB,cAAA,UAAwB,IAAA,CAAK,sBAAsB;iBAanD,UAAA;AAbA,iBAiBA,kBAAA,CAAA,CAjBc,EAAA,MAAA;AAAA,iBAqBd,iBAAA,CAAA,CArBc,EAAA,MAAA;AAAU,iBAyBxB,iBAAA,CAAA,CAzB6B,EAAA,MAAA;AAqB7B,iBAmCA,mBAAA,CAnCiB,CAAA,EAmCM,eAnCN,CAAA,EAAA,CAAA,IAmC6B,iBAnC7B;AAIjB,iBAmCA,eAAA,CAnCiB,OAAA,EAAA,MAAA,EAAA,KAAA,EAAA,MAAA,EAAA,SAAA,EAAA,MAAA,EAAA,SAAA,CAAA,EAAA,MAAA,CAAA,EAwC9B,QAxC8B,EAAA;AA+BjB,iBA6DA,mBAAA,CA7DmB,SAAA,EA8DtB,QA9DsB,EAAA,EAAA,KAAA,EAAA,MAAA,EAAA,SAAA,EAAA,MAAA,CAAA,EAiEhC,QAjEgC,EAAA;;;;AAkXnC;AAgBA;AAUgB,iBA1BA,8BAAA,CA6Bb,KAAA,EAAoB,MAAA,EAAA,UAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA,EAAA;UAbN,oBAAA;;;;;;;;;;;;;;;;iBAUD,sBAAA,yCAGb"}