@copilotkit/aimock 1.40.0 → 1.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +28 -1
  4. package/README.md +5 -3
  5. package/dist/a2a-mock.cjs +18 -11
  6. package/dist/a2a-mock.cjs.map +1 -1
  7. package/dist/a2a-mock.d.cts +1 -0
  8. package/dist/a2a-mock.d.cts.map +1 -1
  9. package/dist/a2a-mock.d.ts +1 -0
  10. package/dist/a2a-mock.d.ts.map +1 -1
  11. package/dist/a2a-mock.js +18 -11
  12. package/dist/a2a-mock.js.map +1 -1
  13. package/dist/agui-handler.d.cts.map +1 -1
  14. package/dist/agui-handler.d.ts.map +1 -1
  15. package/dist/agui-mock.cjs +2 -0
  16. package/dist/agui-mock.cjs.map +1 -1
  17. package/dist/agui-mock.d.cts.map +1 -1
  18. package/dist/agui-mock.d.ts.map +1 -1
  19. package/dist/agui-mock.js +2 -0
  20. package/dist/agui-mock.js.map +1 -1
  21. package/dist/bedrock-converse.cjs +36 -2
  22. package/dist/bedrock-converse.cjs.map +1 -1
  23. package/dist/bedrock-converse.d.cts.map +1 -1
  24. package/dist/bedrock-converse.d.ts.map +1 -1
  25. package/dist/bedrock-converse.js +37 -3
  26. package/dist/bedrock-converse.js.map +1 -1
  27. package/dist/bedrock.cjs +50 -13
  28. package/dist/bedrock.cjs.map +1 -1
  29. package/dist/bedrock.d.cts.map +1 -1
  30. package/dist/bedrock.d.ts.map +1 -1
  31. package/dist/bedrock.js +51 -14
  32. package/dist/bedrock.js.map +1 -1
  33. package/dist/byteplus-video.cjs +954 -0
  34. package/dist/byteplus-video.cjs.map +1 -0
  35. package/dist/byteplus-video.d.cts +176 -0
  36. package/dist/byteplus-video.d.cts.map +1 -0
  37. package/dist/byteplus-video.d.ts +176 -0
  38. package/dist/byteplus-video.d.ts.map +1 -0
  39. package/dist/byteplus-video.js +947 -0
  40. package/dist/byteplus-video.js.map +1 -0
  41. package/dist/chaos.cjs +36 -7
  42. package/dist/chaos.cjs.map +1 -1
  43. package/dist/chaos.d.cts +8 -3
  44. package/dist/chaos.d.cts.map +1 -1
  45. package/dist/chaos.d.ts +8 -3
  46. package/dist/chaos.d.ts.map +1 -1
  47. package/dist/chaos.js +36 -8
  48. package/dist/chaos.js.map +1 -1
  49. package/dist/cli.cjs +5 -1
  50. package/dist/cli.cjs.map +1 -1
  51. package/dist/cli.js +5 -1
  52. package/dist/cli.js.map +1 -1
  53. package/dist/cohere.cjs +58 -5
  54. package/dist/cohere.cjs.map +1 -1
  55. package/dist/cohere.d.cts.map +1 -1
  56. package/dist/cohere.d.ts.map +1 -1
  57. package/dist/cohere.js +59 -6
  58. package/dist/cohere.js.map +1 -1
  59. package/dist/config-loader.d.cts.map +1 -1
  60. package/dist/elevenlabs-audio.cjs +36 -2
  61. package/dist/elevenlabs-audio.cjs.map +1 -1
  62. package/dist/elevenlabs-audio.d.cts.map +1 -1
  63. package/dist/elevenlabs-audio.d.ts.map +1 -1
  64. package/dist/elevenlabs-audio.js +37 -3
  65. package/dist/elevenlabs-audio.js.map +1 -1
  66. package/dist/embeddings.cjs +58 -3
  67. package/dist/embeddings.cjs.map +1 -1
  68. package/dist/embeddings.d.cts.map +1 -1
  69. package/dist/embeddings.d.ts.map +1 -1
  70. package/dist/embeddings.js +59 -4
  71. package/dist/embeddings.js.map +1 -1
  72. package/dist/fal-audio.cjs +38 -2
  73. package/dist/fal-audio.cjs.map +1 -1
  74. package/dist/fal-audio.d.cts.map +1 -1
  75. package/dist/fal-audio.d.ts.map +1 -1
  76. package/dist/fal-audio.js +39 -3
  77. package/dist/fal-audio.js.map +1 -1
  78. package/dist/fixture-loader.cjs +0 -0
  79. package/dist/fixture-loader.cjs.map +1 -1
  80. package/dist/fixture-loader.d.cts +9 -1
  81. package/dist/fixture-loader.d.cts.map +1 -1
  82. package/dist/fixture-loader.d.ts +9 -1
  83. package/dist/fixture-loader.d.ts.map +1 -1
  84. package/dist/fixture-loader.js +0 -0
  85. package/dist/fixture-loader.js.map +1 -1
  86. package/dist/gemini-embeddings.cjs +19 -1
  87. package/dist/gemini-embeddings.cjs.map +1 -1
  88. package/dist/gemini-embeddings.js +20 -2
  89. package/dist/gemini-embeddings.js.map +1 -1
  90. package/dist/gemini-interactions.cjs +15 -1
  91. package/dist/gemini-interactions.cjs.map +1 -1
  92. package/dist/gemini-interactions.d.cts.map +1 -1
  93. package/dist/gemini-interactions.d.ts.map +1 -1
  94. package/dist/gemini-interactions.js +16 -2
  95. package/dist/gemini-interactions.js.map +1 -1
  96. package/dist/gemini.cjs +70 -6
  97. package/dist/gemini.cjs.map +1 -1
  98. package/dist/gemini.d.cts.map +1 -1
  99. package/dist/gemini.d.ts.map +1 -1
  100. package/dist/gemini.js +71 -7
  101. package/dist/gemini.js.map +1 -1
  102. package/dist/grok-video.cjs +2 -2
  103. package/dist/grok-video.cjs.map +1 -1
  104. package/dist/grok-video.d.cts.map +1 -1
  105. package/dist/grok-video.d.ts.map +1 -1
  106. package/dist/grok-video.js +2 -2
  107. package/dist/grok-video.js.map +1 -1
  108. package/dist/helpers.cjs +160 -5
  109. package/dist/helpers.cjs.map +1 -1
  110. package/dist/helpers.d.cts.map +1 -1
  111. package/dist/helpers.d.ts.map +1 -1
  112. package/dist/helpers.js +152 -6
  113. package/dist/helpers.js.map +1 -1
  114. package/dist/images.cjs +24 -6
  115. package/dist/images.cjs.map +1 -1
  116. package/dist/images.d.cts +11 -3
  117. package/dist/images.d.cts.map +1 -1
  118. package/dist/images.d.ts +11 -3
  119. package/dist/images.d.ts.map +1 -1
  120. package/dist/images.js +25 -7
  121. package/dist/images.js.map +1 -1
  122. package/dist/index.cjs +7 -0
  123. package/dist/index.d.cts +2 -1
  124. package/dist/index.d.ts +2 -1
  125. package/dist/index.js +2 -1
  126. package/dist/llmock.cjs +15 -0
  127. package/dist/llmock.cjs.map +1 -1
  128. package/dist/llmock.d.cts +12 -0
  129. package/dist/llmock.d.cts.map +1 -1
  130. package/dist/llmock.d.ts +12 -0
  131. package/dist/llmock.d.ts.map +1 -1
  132. package/dist/llmock.js +16 -1
  133. package/dist/llmock.js.map +1 -1
  134. package/dist/mcp-mock.cjs +11 -0
  135. package/dist/mcp-mock.cjs.map +1 -1
  136. package/dist/mcp-mock.d.cts.map +1 -1
  137. package/dist/mcp-mock.d.ts.map +1 -1
  138. package/dist/mcp-mock.js +11 -0
  139. package/dist/mcp-mock.js.map +1 -1
  140. package/dist/messages.cjs +18 -1
  141. package/dist/messages.cjs.map +1 -1
  142. package/dist/messages.d.cts.map +1 -1
  143. package/dist/messages.d.ts.map +1 -1
  144. package/dist/messages.js +19 -2
  145. package/dist/messages.js.map +1 -1
  146. package/dist/metrics.cjs +20 -0
  147. package/dist/metrics.cjs.map +1 -1
  148. package/dist/metrics.d.cts.map +1 -1
  149. package/dist/metrics.d.ts.map +1 -1
  150. package/dist/metrics.js +19 -1
  151. package/dist/metrics.js.map +1 -1
  152. package/dist/moderation.cjs +67 -3
  153. package/dist/moderation.cjs.map +1 -1
  154. package/dist/moderation.d.cts +1 -0
  155. package/dist/moderation.d.cts.map +1 -1
  156. package/dist/moderation.d.ts +1 -0
  157. package/dist/moderation.d.ts.map +1 -1
  158. package/dist/moderation.js +68 -4
  159. package/dist/moderation.js.map +1 -1
  160. package/dist/ollama.cjs +75 -5
  161. package/dist/ollama.cjs.map +1 -1
  162. package/dist/ollama.d.cts.map +1 -1
  163. package/dist/ollama.d.ts.map +1 -1
  164. package/dist/ollama.js +76 -6
  165. package/dist/ollama.js.map +1 -1
  166. package/dist/openrouter-video.cjs +4 -4
  167. package/dist/openrouter-video.cjs.map +1 -1
  168. package/dist/openrouter-video.d.cts.map +1 -1
  169. package/dist/openrouter-video.d.ts.map +1 -1
  170. package/dist/openrouter-video.js +4 -4
  171. package/dist/openrouter-video.js.map +1 -1
  172. package/dist/provider-auth.cjs +2 -0
  173. package/dist/provider-auth.cjs.map +1 -1
  174. package/dist/provider-auth.js +2 -0
  175. package/dist/provider-auth.js.map +1 -1
  176. package/dist/rerank.cjs +65 -2
  177. package/dist/rerank.cjs.map +1 -1
  178. package/dist/rerank.d.cts +1 -1
  179. package/dist/rerank.d.cts.map +1 -1
  180. package/dist/rerank.d.ts +1 -1
  181. package/dist/rerank.d.ts.map +1 -1
  182. package/dist/rerank.js +66 -3
  183. package/dist/rerank.js.map +1 -1
  184. package/dist/responses.cjs +18 -1
  185. package/dist/responses.cjs.map +1 -1
  186. package/dist/responses.d.cts.map +1 -1
  187. package/dist/responses.d.ts.map +1 -1
  188. package/dist/responses.js +19 -2
  189. package/dist/responses.js.map +1 -1
  190. package/dist/router.cjs +1 -0
  191. package/dist/router.cjs.map +1 -1
  192. package/dist/router.d.cts.map +1 -1
  193. package/dist/router.d.ts.map +1 -1
  194. package/dist/router.js +1 -0
  195. package/dist/router.js.map +1 -1
  196. package/dist/search.cjs +67 -3
  197. package/dist/search.cjs.map +1 -1
  198. package/dist/search.d.cts +1 -0
  199. package/dist/search.d.cts.map +1 -1
  200. package/dist/search.d.ts +1 -0
  201. package/dist/search.d.ts.map +1 -1
  202. package/dist/search.js +68 -4
  203. package/dist/search.js.map +1 -1
  204. package/dist/server.cjs +406 -17
  205. package/dist/server.cjs.map +1 -1
  206. package/dist/server.d.cts +2 -0
  207. package/dist/server.d.cts.map +1 -1
  208. package/dist/server.d.ts +2 -0
  209. package/dist/server.d.ts.map +1 -1
  210. package/dist/server.js +410 -21
  211. package/dist/server.js.map +1 -1
  212. package/dist/speech.cjs +18 -1
  213. package/dist/speech.cjs.map +1 -1
  214. package/dist/speech.d.cts.map +1 -1
  215. package/dist/speech.d.ts.map +1 -1
  216. package/dist/speech.js +19 -2
  217. package/dist/speech.js.map +1 -1
  218. package/dist/stream-collapse.cjs +2 -1
  219. package/dist/stream-collapse.cjs.map +1 -1
  220. package/dist/stream-collapse.js +2 -1
  221. package/dist/stream-collapse.js.map +1 -1
  222. package/dist/transcription.cjs +21 -2
  223. package/dist/transcription.cjs.map +1 -1
  224. package/dist/transcription.d.cts.map +1 -1
  225. package/dist/transcription.d.ts.map +1 -1
  226. package/dist/transcription.js +21 -2
  227. package/dist/transcription.js.map +1 -1
  228. package/dist/types.d.cts +58 -5
  229. package/dist/types.d.cts.map +1 -1
  230. package/dist/types.d.ts +58 -5
  231. package/dist/types.d.ts.map +1 -1
  232. package/dist/vector-mock.cjs +88 -0
  233. package/dist/vector-mock.cjs.map +1 -1
  234. package/dist/vector-mock.d.cts.map +1 -1
  235. package/dist/vector-mock.d.ts.map +1 -1
  236. package/dist/vector-mock.js +89 -1
  237. package/dist/vector-mock.js.map +1 -1
  238. package/dist/vector-types.d.cts.map +1 -1
  239. package/dist/veo-video.cjs +2 -2
  240. package/dist/veo-video.cjs.map +1 -1
  241. package/dist/veo-video.d.cts.map +1 -1
  242. package/dist/veo-video.d.ts.map +1 -1
  243. package/dist/veo-video.js +2 -2
  244. package/dist/veo-video.js.map +1 -1
  245. package/dist/video.cjs +19 -2
  246. package/dist/video.cjs.map +1 -1
  247. package/dist/video.d.cts.map +1 -1
  248. package/dist/video.d.ts.map +1 -1
  249. package/dist/video.js +20 -3
  250. package/dist/video.js.map +1 -1
  251. package/package.json +2 -2
@@ -22,6 +22,7 @@ const PROVIDER_AUTH_SCHEMES = {
22
22
  openrouter: { kind: "bearer" },
23
23
  cohere: { kind: "bearer" },
24
24
  grok: { kind: "bearer" },
25
+ byteplus: { kind: "bearer" },
25
26
  ollama: { kind: "bearer" },
26
27
  anthropic: { kind: "x-api-key" },
27
28
  gemini: { kind: "x-goog-api-key" },
@@ -175,6 +176,7 @@ function readProviderKeysFromEnv(env = process.env) {
175
176
  if (env.AIMOCK_PROVIDER_OPENROUTER_KEY) keys.openrouter = env.AIMOCK_PROVIDER_OPENROUTER_KEY;
176
177
  if (env.AIMOCK_PROVIDER_COHERE_KEY) keys.cohere = env.AIMOCK_PROVIDER_COHERE_KEY;
177
178
  if (env.AIMOCK_PROVIDER_GROK_KEY) keys.grok = env.AIMOCK_PROVIDER_GROK_KEY;
179
+ if (env.AIMOCK_PROVIDER_BYTEPLUS_KEY) keys.byteplus = env.AIMOCK_PROVIDER_BYTEPLUS_KEY;
178
180
  if (env.AIMOCK_PROVIDER_OLLAMA_KEY) keys.ollama = env.AIMOCK_PROVIDER_OLLAMA_KEY;
179
181
  if (env.AIMOCK_PROVIDER_VEO_KEY) keys.veo = env.AIMOCK_PROVIDER_VEO_KEY;
180
182
  if (env.AIMOCK_PROVIDER_AZURE_KEY) keys.azure = env.AIMOCK_PROVIDER_AZURE_KEY;
@@ -1 +1 @@
1
- {"version":3,"file":"provider-auth.cjs","names":[],"sources":["../src/provider-auth.ts"],"sourcesContent":["import type { RecordProviderKey } from \"./types.js\";\n\n/**\n * Default marker prefix identifying a \"dummy\" credential that a caller sends\n * only to satisfy an SDK's non-empty API-key requirement (e.g. `sk-aimock-...`).\n * A caller credential that is absent OR begins with this prefix is treated as a\n * placeholder that aimock is free to override with its own built-in upstream key.\n * A caller credential that does NOT begin with this prefix is treated as a real\n * key and forwarded verbatim (the caller overrides aimock).\n *\n * Overridable via the `AIMOCK_DUMMY_KEY_MARKER` env var for setups that mint\n * placeholder keys under a different prefix.\n */\nexport const DEFAULT_DUMMY_KEY_MARKER = \"sk-aimock-\";\n\n/** Resolve the active dummy-key marker, honoring the env override. */\nexport function getDummyKeyMarker(): string {\n const override = process.env.AIMOCK_DUMMY_KEY_MARKER;\n return override && override.length > 0 ? override : DEFAULT_DUMMY_KEY_MARKER;\n}\n\n/**\n * How a given provider carries its API credential on the wire. aimock injects\n * its built-in key using the provider's native scheme so the upstream accepts\n * it. Providers whose auth is a signed/exchanged credential (Bedrock SigV4,\n * Vertex AI OAuth) are deliberately absent — those are NOT simple static-key\n * schemes, so aimock never rewrites their auth and always forwards the caller's\n * credential unchanged.\n *\n * Scheme kinds (all static long-lived keys):\n * - bearer `Authorization: Bearer <key>` (OpenAI, OpenRouter, Cohere, Grok/xAI, Ollama)\n * - fal-key `Authorization: Key <key>` (fal.ai — note the `Key ` prefix, NOT `Bearer`)\n * - x-api-key `x-api-key: <key>` (Anthropic)\n * - x-goog-api-key `x-goog-api-key: <key>` (Gemini, Gemini Interactions, Veo)\n * - api-key `api-key: <key>` (Azure OpenAI static-key auth)\n * - xi-api-key `xi-api-key: <key>` (ElevenLabs)\n *\n * Note on Azure: Azure OpenAI also supports Microsoft Entra ID `Authorization:\n * Bearer <token>` OAuth. aimock only ever injects the static `api-key` header,\n * and a real Entra bearer token from the caller is never dummy-prefixed, so it\n * is always forwarded verbatim (the caller overrides aimock).\n */\nexport type AuthScheme =\n | { kind: \"bearer\" } // Authorization: Bearer <key>\n | { kind: \"fal-key\" } // Authorization: Key <key>\n | { kind: \"x-api-key\" } // x-api-key: <key>\n | { kind: \"x-goog-api-key\" } // x-goog-api-key: <key>\n | { kind: \"api-key\" } // api-key: <key>\n | { kind: \"xi-api-key\" }; // xi-api-key: <key>\n\nconst PROVIDER_AUTH_SCHEMES: Partial<Record<RecordProviderKey, AuthScheme>> = {\n // Bearer-token providers.\n openai: { kind: \"bearer\" },\n openrouter: { kind: \"bearer\" },\n cohere: { kind: \"bearer\" },\n grok: { kind: \"bearer\" }, // xAI\n ollama: { kind: \"bearer\" }, // Ollama Cloud / bearer-gated Ollama servers\n // Anthropic.\n anthropic: { kind: \"x-api-key\" },\n // Google (Gemini + Veo) use the x-goog-api-key header scheme.\n gemini: { kind: \"x-goog-api-key\" },\n \"gemini-interactions\": { kind: \"x-goog-api-key\" },\n veo: { kind: \"x-goog-api-key\" },\n // Azure OpenAI static-key auth.\n azure: { kind: \"api-key\" },\n // ElevenLabs.\n elevenlabs: { kind: \"xi-api-key\" },\n // fal.ai — uses the `Key ` prefix on Authorization, NOT `Bearer `.\n fal: { kind: \"fal-key\" },\n};\n\nexport function hasStaticProviderAuthScheme(providerKey: RecordProviderKey): boolean {\n return PROVIDER_AUTH_SCHEMES[providerKey] !== undefined;\n}\n\n/** Force a known provider credential onto an already-scrubbed outbound map. */\nexport function applyConfiguredProviderAuth(\n forwardHeaders: Record<string, string>,\n target: URL,\n providerKey: RecordProviderKey,\n providerCredential: string,\n): boolean {\n void target;\n const scheme = PROVIDER_AUTH_SCHEMES[providerKey];\n if (!scheme) return false;\n switch (scheme.kind) {\n case \"bearer\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders.Authorization = `Bearer ${providerCredential}`;\n break;\n case \"fal-key\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders.Authorization = `Key ${providerCredential}`;\n break;\n default:\n takeHeader(forwardHeaders, authHeaderName(scheme));\n forwardHeaders[authHeaderName(scheme)] = providerCredential;\n }\n return true;\n}\n\n/**\n * Case-insensitively delete a header from a forward-header map, returning the\n * value that was present (if any). Header maps preserve the original casing of\n * incoming headers, so we can't assume a canonical key.\n */\nfunction takeHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n let found: string | undefined;\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lowerName) {\n found = headers[key];\n delete headers[key];\n }\n }\n return found;\n}\n\n/**\n * Extract the caller's credential value for a given auth scheme from the\n * forwarded headers, if present. For bearer, strips the `Bearer ` prefix.\n */\nfunction readCallerCredential(\n headers: Record<string, string>,\n scheme: AuthScheme,\n): string | undefined {\n if (scheme.kind === \"bearer\" || scheme.kind === \"fal-key\") {\n const raw = scanHeader(headers, \"authorization\");\n if (raw === undefined) return undefined;\n // Strip the scheme prefix (`Bearer ` for bearer, `Key ` for fal) if present,\n // otherwise treat the whole value as the credential.\n const prefix = scheme.kind === \"fal-key\" ? \"Key\" : \"Bearer\";\n const match = new RegExp(`^\\\\s*${prefix}\\\\s+(.+)$`, \"i\").exec(raw);\n return match ? match[1].trim() : raw.trim();\n }\n return scanHeader(headers, authHeaderName(scheme));\n}\n\n/**\n * The wire header name a non-Authorization scheme carries its credential in.\n * (bearer/fal-key both use `Authorization` and are handled separately.)\n */\nfunction authHeaderName(scheme: AuthScheme): string {\n switch (scheme.kind) {\n case \"x-api-key\":\n return \"x-api-key\";\n case \"x-goog-api-key\":\n return \"x-goog-api-key\";\n case \"api-key\":\n return \"api-key\";\n case \"xi-api-key\":\n return \"xi-api-key\";\n case \"bearer\":\n case \"fal-key\":\n return \"authorization\";\n }\n}\n\n/** Case-insensitive header lookup that does not mutate the map. */\nfunction scanHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lowerName) return headers[key];\n }\n return undefined;\n}\n\n/**\n * Decide whether aimock should override the caller's credential with its own\n * built-in key. Override when the caller sent no credential OR sent a dummy\n * placeholder (prefixed with the active marker). A real caller key is left\n * untouched so the caller can always override aimock.\n */\nfunction shouldOverride(callerCredential: string | undefined, marker: string): boolean {\n if (callerCredential === undefined || callerCredential.length === 0) return true;\n return callerCredential.startsWith(marker);\n}\n\n/**\n * Inject aimock's built-in upstream credential into the forwarded headers when\n * appropriate (opt-in, backward-compatible). Mutates `forwardHeaders` in place\n * and may mutate `target` (query-param schemes; none currently).\n *\n * Precedence:\n * - No built-in key configured for this provider → no-op (forward verbatim).\n * - Provider not a static-key scheme → no-op (Bedrock SigV4 / Vertex AI OAuth).\n * - Caller credential absent or dummy-prefixed → inject built-in key.\n * - Caller credential is a real key → forward it unchanged.\n *\n * @param forwardHeaders headers already built by buildForwardHeaders(req)\n * @param target upstream URL (reserved for query-param auth schemes)\n * @param providerKey which provider this request is routed to\n * @param builtinKey aimock's configured key for this provider (if any)\n */\nexport function applyProviderAuth(\n forwardHeaders: Record<string, string>,\n target: URL,\n providerKey: RecordProviderKey,\n builtinKey: string | undefined,\n): void {\n void target; // reserved for future query-param schemes (e.g. Gemini ?key=)\n if (!builtinKey) return; // feature inert for this provider\n\n const scheme = PROVIDER_AUTH_SCHEMES[providerKey];\n if (!scheme) return; // signed/OAuth provider — never rewrite auth\n\n const marker = getDummyKeyMarker();\n const callerCredential = readCallerCredential(forwardHeaders, scheme);\n if (!shouldOverride(callerCredential, marker)) return; // caller sent a real key\n\n // Strip any existing auth headers for this scheme (across casings) before\n // setting aimock's key, so a dummy caller key can't linger alongside ours.\n switch (scheme.kind) {\n case \"bearer\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders[\"Authorization\"] = `Bearer ${builtinKey}`;\n break;\n case \"fal-key\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders[\"Authorization\"] = `Key ${builtinKey}`;\n break;\n case \"x-api-key\":\n takeHeader(forwardHeaders, \"x-api-key\");\n forwardHeaders[\"x-api-key\"] = builtinKey;\n break;\n case \"x-goog-api-key\":\n takeHeader(forwardHeaders, \"x-goog-api-key\");\n forwardHeaders[\"x-goog-api-key\"] = builtinKey;\n break;\n case \"api-key\":\n takeHeader(forwardHeaders, \"api-key\");\n forwardHeaders[\"api-key\"] = builtinKey;\n break;\n case \"xi-api-key\":\n takeHeader(forwardHeaders, \"xi-api-key\");\n forwardHeaders[\"xi-api-key\"] = builtinKey;\n break;\n }\n}\n\n/**\n * Read per-provider built-in keys from the environment. Returns undefined when\n * no provider key is configured so the feature stays fully inert by default.\n *\n * Each wired static-key provider reads `AIMOCK_PROVIDER_<PROVIDER>_KEY`. An\n * empty-string value is treated as unset (existing truthiness pattern), keeping\n * the feature inert. `gemini-interactions` is not read here: it reuses the\n * Gemini key via the lookup-key remap in `proxyAndRecord`. Signed/OAuth\n * providers (`bedrock`, `vertexai`) have no env var — aimock never rewrites\n * their auth.\n */\nexport function readProviderKeysFromEnv(\n env: NodeJS.ProcessEnv = process.env,\n): Partial<Record<RecordProviderKey, string>> | undefined {\n const keys: Partial<Record<RecordProviderKey, string>> = {};\n if (env.AIMOCK_PROVIDER_OPENAI_KEY) keys.openai = env.AIMOCK_PROVIDER_OPENAI_KEY;\n if (env.AIMOCK_PROVIDER_ANTHROPIC_KEY) keys.anthropic = env.AIMOCK_PROVIDER_ANTHROPIC_KEY;\n if (env.AIMOCK_PROVIDER_GEMINI_KEY) keys.gemini = env.AIMOCK_PROVIDER_GEMINI_KEY;\n if (env.AIMOCK_PROVIDER_OPENROUTER_KEY) keys.openrouter = env.AIMOCK_PROVIDER_OPENROUTER_KEY;\n if (env.AIMOCK_PROVIDER_COHERE_KEY) keys.cohere = env.AIMOCK_PROVIDER_COHERE_KEY;\n if (env.AIMOCK_PROVIDER_GROK_KEY) keys.grok = env.AIMOCK_PROVIDER_GROK_KEY;\n if (env.AIMOCK_PROVIDER_OLLAMA_KEY) keys.ollama = env.AIMOCK_PROVIDER_OLLAMA_KEY;\n if (env.AIMOCK_PROVIDER_VEO_KEY) keys.veo = env.AIMOCK_PROVIDER_VEO_KEY;\n if (env.AIMOCK_PROVIDER_AZURE_KEY) keys.azure = env.AIMOCK_PROVIDER_AZURE_KEY;\n if (env.AIMOCK_PROVIDER_ELEVENLABS_KEY) keys.elevenlabs = env.AIMOCK_PROVIDER_ELEVENLABS_KEY;\n if (env.AIMOCK_PROVIDER_FAL_KEY) keys.fal = env.AIMOCK_PROVIDER_FAL_KEY;\n return Object.keys(keys).length > 0 ? keys : undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"provider-auth.cjs","names":[],"sources":["../src/provider-auth.ts"],"sourcesContent":["import type { RecordProviderKey } from \"./types.js\";\n\n/**\n * Default marker prefix identifying a \"dummy\" credential that a caller sends\n * only to satisfy an SDK's non-empty API-key requirement (e.g. `sk-aimock-...`).\n * A caller credential that is absent OR begins with this prefix is treated as a\n * placeholder that aimock is free to override with its own built-in upstream key.\n * A caller credential that does NOT begin with this prefix is treated as a real\n * key and forwarded verbatim (the caller overrides aimock).\n *\n * Overridable via the `AIMOCK_DUMMY_KEY_MARKER` env var for setups that mint\n * placeholder keys under a different prefix.\n */\nexport const DEFAULT_DUMMY_KEY_MARKER = \"sk-aimock-\";\n\n/** Resolve the active dummy-key marker, honoring the env override. */\nexport function getDummyKeyMarker(): string {\n const override = process.env.AIMOCK_DUMMY_KEY_MARKER;\n return override && override.length > 0 ? override : DEFAULT_DUMMY_KEY_MARKER;\n}\n\n/**\n * How a given provider carries its API credential on the wire. aimock injects\n * its built-in key using the provider's native scheme so the upstream accepts\n * it. Providers whose auth is a signed/exchanged credential (Bedrock SigV4,\n * Vertex AI OAuth) are deliberately absent — those are NOT simple static-key\n * schemes, so aimock never rewrites their auth and always forwards the caller's\n * credential unchanged.\n *\n * Scheme kinds (all static long-lived keys):\n * - bearer `Authorization: Bearer <key>` (OpenAI, OpenRouter, Cohere, Grok/xAI, BytePlus Ark, Ollama)\n * - fal-key `Authorization: Key <key>` (fal.ai — note the `Key ` prefix, NOT `Bearer`)\n * - x-api-key `x-api-key: <key>` (Anthropic)\n * - x-goog-api-key `x-goog-api-key: <key>` (Gemini, Gemini Interactions, Veo)\n * - api-key `api-key: <key>` (Azure OpenAI static-key auth)\n * - xi-api-key `xi-api-key: <key>` (ElevenLabs)\n *\n * Note on Azure: Azure OpenAI also supports Microsoft Entra ID `Authorization:\n * Bearer <token>` OAuth. aimock only ever injects the static `api-key` header,\n * and a real Entra bearer token from the caller is never dummy-prefixed, so it\n * is always forwarded verbatim (the caller overrides aimock).\n */\nexport type AuthScheme =\n | { kind: \"bearer\" } // Authorization: Bearer <key>\n | { kind: \"fal-key\" } // Authorization: Key <key>\n | { kind: \"x-api-key\" } // x-api-key: <key>\n | { kind: \"x-goog-api-key\" } // x-goog-api-key: <key>\n | { kind: \"api-key\" } // api-key: <key>\n | { kind: \"xi-api-key\" }; // xi-api-key: <key>\n\nconst PROVIDER_AUTH_SCHEMES: Partial<Record<RecordProviderKey, AuthScheme>> = {\n // Bearer-token providers.\n openai: { kind: \"bearer\" },\n openrouter: { kind: \"bearer\" },\n cohere: { kind: \"bearer\" },\n grok: { kind: \"bearer\" }, // xAI\n byteplus: { kind: \"bearer\" }, // BytePlus Ark (ModelArk) — Authorization: Bearer $ARK_API_KEY\n ollama: { kind: \"bearer\" }, // Ollama Cloud / bearer-gated Ollama servers\n // Anthropic.\n anthropic: { kind: \"x-api-key\" },\n // Google (Gemini + Veo) use the x-goog-api-key header scheme.\n gemini: { kind: \"x-goog-api-key\" },\n \"gemini-interactions\": { kind: \"x-goog-api-key\" },\n veo: { kind: \"x-goog-api-key\" },\n // Azure OpenAI static-key auth.\n azure: { kind: \"api-key\" },\n // ElevenLabs.\n elevenlabs: { kind: \"xi-api-key\" },\n // fal.ai — uses the `Key ` prefix on Authorization, NOT `Bearer `.\n fal: { kind: \"fal-key\" },\n};\n\nexport function hasStaticProviderAuthScheme(providerKey: RecordProviderKey): boolean {\n return PROVIDER_AUTH_SCHEMES[providerKey] !== undefined;\n}\n\n/** Force a known provider credential onto an already-scrubbed outbound map. */\nexport function applyConfiguredProviderAuth(\n forwardHeaders: Record<string, string>,\n target: URL,\n providerKey: RecordProviderKey,\n providerCredential: string,\n): boolean {\n void target;\n const scheme = PROVIDER_AUTH_SCHEMES[providerKey];\n if (!scheme) return false;\n switch (scheme.kind) {\n case \"bearer\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders.Authorization = `Bearer ${providerCredential}`;\n break;\n case \"fal-key\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders.Authorization = `Key ${providerCredential}`;\n break;\n default:\n takeHeader(forwardHeaders, authHeaderName(scheme));\n forwardHeaders[authHeaderName(scheme)] = providerCredential;\n }\n return true;\n}\n\n/**\n * Case-insensitively delete a header from a forward-header map, returning the\n * value that was present (if any). Header maps preserve the original casing of\n * incoming headers, so we can't assume a canonical key.\n */\nfunction takeHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n let found: string | undefined;\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lowerName) {\n found = headers[key];\n delete headers[key];\n }\n }\n return found;\n}\n\n/**\n * Extract the caller's credential value for a given auth scheme from the\n * forwarded headers, if present. For bearer, strips the `Bearer ` prefix.\n */\nfunction readCallerCredential(\n headers: Record<string, string>,\n scheme: AuthScheme,\n): string | undefined {\n if (scheme.kind === \"bearer\" || scheme.kind === \"fal-key\") {\n const raw = scanHeader(headers, \"authorization\");\n if (raw === undefined) return undefined;\n // Strip the scheme prefix (`Bearer ` for bearer, `Key ` for fal) if present,\n // otherwise treat the whole value as the credential.\n const prefix = scheme.kind === \"fal-key\" ? \"Key\" : \"Bearer\";\n const match = new RegExp(`^\\\\s*${prefix}\\\\s+(.+)$`, \"i\").exec(raw);\n return match ? match[1].trim() : raw.trim();\n }\n return scanHeader(headers, authHeaderName(scheme));\n}\n\n/**\n * The wire header name a non-Authorization scheme carries its credential in.\n * (bearer/fal-key both use `Authorization` and are handled separately.)\n */\nfunction authHeaderName(scheme: AuthScheme): string {\n switch (scheme.kind) {\n case \"x-api-key\":\n return \"x-api-key\";\n case \"x-goog-api-key\":\n return \"x-goog-api-key\";\n case \"api-key\":\n return \"api-key\";\n case \"xi-api-key\":\n return \"xi-api-key\";\n case \"bearer\":\n case \"fal-key\":\n return \"authorization\";\n }\n}\n\n/** Case-insensitive header lookup that does not mutate the map. */\nfunction scanHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lowerName) return headers[key];\n }\n return undefined;\n}\n\n/**\n * Decide whether aimock should override the caller's credential with its own\n * built-in key. Override when the caller sent no credential OR sent a dummy\n * placeholder (prefixed with the active marker). A real caller key is left\n * untouched so the caller can always override aimock.\n */\nfunction shouldOverride(callerCredential: string | undefined, marker: string): boolean {\n if (callerCredential === undefined || callerCredential.length === 0) return true;\n return callerCredential.startsWith(marker);\n}\n\n/**\n * Inject aimock's built-in upstream credential into the forwarded headers when\n * appropriate (opt-in, backward-compatible). Mutates `forwardHeaders` in place\n * and may mutate `target` (query-param schemes; none currently).\n *\n * Precedence:\n * - No built-in key configured for this provider → no-op (forward verbatim).\n * - Provider not a static-key scheme → no-op (Bedrock SigV4 / Vertex AI OAuth).\n * - Caller credential absent or dummy-prefixed → inject built-in key.\n * - Caller credential is a real key → forward it unchanged.\n *\n * @param forwardHeaders headers already built by buildForwardHeaders(req)\n * @param target upstream URL (reserved for query-param auth schemes)\n * @param providerKey which provider this request is routed to\n * @param builtinKey aimock's configured key for this provider (if any)\n */\nexport function applyProviderAuth(\n forwardHeaders: Record<string, string>,\n target: URL,\n providerKey: RecordProviderKey,\n builtinKey: string | undefined,\n): void {\n void target; // reserved for future query-param schemes (e.g. Gemini ?key=)\n if (!builtinKey) return; // feature inert for this provider\n\n const scheme = PROVIDER_AUTH_SCHEMES[providerKey];\n if (!scheme) return; // signed/OAuth provider — never rewrite auth\n\n const marker = getDummyKeyMarker();\n const callerCredential = readCallerCredential(forwardHeaders, scheme);\n if (!shouldOverride(callerCredential, marker)) return; // caller sent a real key\n\n // Strip any existing auth headers for this scheme (across casings) before\n // setting aimock's key, so a dummy caller key can't linger alongside ours.\n switch (scheme.kind) {\n case \"bearer\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders[\"Authorization\"] = `Bearer ${builtinKey}`;\n break;\n case \"fal-key\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders[\"Authorization\"] = `Key ${builtinKey}`;\n break;\n case \"x-api-key\":\n takeHeader(forwardHeaders, \"x-api-key\");\n forwardHeaders[\"x-api-key\"] = builtinKey;\n break;\n case \"x-goog-api-key\":\n takeHeader(forwardHeaders, \"x-goog-api-key\");\n forwardHeaders[\"x-goog-api-key\"] = builtinKey;\n break;\n case \"api-key\":\n takeHeader(forwardHeaders, \"api-key\");\n forwardHeaders[\"api-key\"] = builtinKey;\n break;\n case \"xi-api-key\":\n takeHeader(forwardHeaders, \"xi-api-key\");\n forwardHeaders[\"xi-api-key\"] = builtinKey;\n break;\n }\n}\n\n/**\n * Read per-provider built-in keys from the environment. Returns undefined when\n * no provider key is configured so the feature stays fully inert by default.\n *\n * Each wired static-key provider reads `AIMOCK_PROVIDER_<PROVIDER>_KEY`. An\n * empty-string value is treated as unset (existing truthiness pattern), keeping\n * the feature inert. `gemini-interactions` is not read here: it reuses the\n * Gemini key via the lookup-key remap in `proxyAndRecord`. Signed/OAuth\n * providers (`bedrock`, `vertexai`) have no env var — aimock never rewrites\n * their auth.\n */\nexport function readProviderKeysFromEnv(\n env: NodeJS.ProcessEnv = process.env,\n): Partial<Record<RecordProviderKey, string>> | undefined {\n const keys: Partial<Record<RecordProviderKey, string>> = {};\n if (env.AIMOCK_PROVIDER_OPENAI_KEY) keys.openai = env.AIMOCK_PROVIDER_OPENAI_KEY;\n if (env.AIMOCK_PROVIDER_ANTHROPIC_KEY) keys.anthropic = env.AIMOCK_PROVIDER_ANTHROPIC_KEY;\n if (env.AIMOCK_PROVIDER_GEMINI_KEY) keys.gemini = env.AIMOCK_PROVIDER_GEMINI_KEY;\n if (env.AIMOCK_PROVIDER_OPENROUTER_KEY) keys.openrouter = env.AIMOCK_PROVIDER_OPENROUTER_KEY;\n if (env.AIMOCK_PROVIDER_COHERE_KEY) keys.cohere = env.AIMOCK_PROVIDER_COHERE_KEY;\n if (env.AIMOCK_PROVIDER_GROK_KEY) keys.grok = env.AIMOCK_PROVIDER_GROK_KEY;\n if (env.AIMOCK_PROVIDER_BYTEPLUS_KEY) keys.byteplus = env.AIMOCK_PROVIDER_BYTEPLUS_KEY;\n if (env.AIMOCK_PROVIDER_OLLAMA_KEY) keys.ollama = env.AIMOCK_PROVIDER_OLLAMA_KEY;\n if (env.AIMOCK_PROVIDER_VEO_KEY) keys.veo = env.AIMOCK_PROVIDER_VEO_KEY;\n if (env.AIMOCK_PROVIDER_AZURE_KEY) keys.azure = env.AIMOCK_PROVIDER_AZURE_KEY;\n if (env.AIMOCK_PROVIDER_ELEVENLABS_KEY) keys.elevenlabs = env.AIMOCK_PROVIDER_ELEVENLABS_KEY;\n if (env.AIMOCK_PROVIDER_FAL_KEY) keys.fal = env.AIMOCK_PROVIDER_FAL_KEY;\n return Object.keys(keys).length > 0 ? keys : undefined;\n}\n"],"mappings":"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"}
@@ -21,6 +21,7 @@ const PROVIDER_AUTH_SCHEMES = {
21
21
  openrouter: { kind: "bearer" },
22
22
  cohere: { kind: "bearer" },
23
23
  grok: { kind: "bearer" },
24
+ byteplus: { kind: "bearer" },
24
25
  ollama: { kind: "bearer" },
25
26
  anthropic: { kind: "x-api-key" },
26
27
  gemini: { kind: "x-goog-api-key" },
@@ -174,6 +175,7 @@ function readProviderKeysFromEnv(env = process.env) {
174
175
  if (env.AIMOCK_PROVIDER_OPENROUTER_KEY) keys.openrouter = env.AIMOCK_PROVIDER_OPENROUTER_KEY;
175
176
  if (env.AIMOCK_PROVIDER_COHERE_KEY) keys.cohere = env.AIMOCK_PROVIDER_COHERE_KEY;
176
177
  if (env.AIMOCK_PROVIDER_GROK_KEY) keys.grok = env.AIMOCK_PROVIDER_GROK_KEY;
178
+ if (env.AIMOCK_PROVIDER_BYTEPLUS_KEY) keys.byteplus = env.AIMOCK_PROVIDER_BYTEPLUS_KEY;
177
179
  if (env.AIMOCK_PROVIDER_OLLAMA_KEY) keys.ollama = env.AIMOCK_PROVIDER_OLLAMA_KEY;
178
180
  if (env.AIMOCK_PROVIDER_VEO_KEY) keys.veo = env.AIMOCK_PROVIDER_VEO_KEY;
179
181
  if (env.AIMOCK_PROVIDER_AZURE_KEY) keys.azure = env.AIMOCK_PROVIDER_AZURE_KEY;
@@ -1 +1 @@
1
- {"version":3,"file":"provider-auth.js","names":[],"sources":["../src/provider-auth.ts"],"sourcesContent":["import type { RecordProviderKey } from \"./types.js\";\n\n/**\n * Default marker prefix identifying a \"dummy\" credential that a caller sends\n * only to satisfy an SDK's non-empty API-key requirement (e.g. `sk-aimock-...`).\n * A caller credential that is absent OR begins with this prefix is treated as a\n * placeholder that aimock is free to override with its own built-in upstream key.\n * A caller credential that does NOT begin with this prefix is treated as a real\n * key and forwarded verbatim (the caller overrides aimock).\n *\n * Overridable via the `AIMOCK_DUMMY_KEY_MARKER` env var for setups that mint\n * placeholder keys under a different prefix.\n */\nexport const DEFAULT_DUMMY_KEY_MARKER = \"sk-aimock-\";\n\n/** Resolve the active dummy-key marker, honoring the env override. */\nexport function getDummyKeyMarker(): string {\n const override = process.env.AIMOCK_DUMMY_KEY_MARKER;\n return override && override.length > 0 ? override : DEFAULT_DUMMY_KEY_MARKER;\n}\n\n/**\n * How a given provider carries its API credential on the wire. aimock injects\n * its built-in key using the provider's native scheme so the upstream accepts\n * it. Providers whose auth is a signed/exchanged credential (Bedrock SigV4,\n * Vertex AI OAuth) are deliberately absent — those are NOT simple static-key\n * schemes, so aimock never rewrites their auth and always forwards the caller's\n * credential unchanged.\n *\n * Scheme kinds (all static long-lived keys):\n * - bearer `Authorization: Bearer <key>` (OpenAI, OpenRouter, Cohere, Grok/xAI, Ollama)\n * - fal-key `Authorization: Key <key>` (fal.ai — note the `Key ` prefix, NOT `Bearer`)\n * - x-api-key `x-api-key: <key>` (Anthropic)\n * - x-goog-api-key `x-goog-api-key: <key>` (Gemini, Gemini Interactions, Veo)\n * - api-key `api-key: <key>` (Azure OpenAI static-key auth)\n * - xi-api-key `xi-api-key: <key>` (ElevenLabs)\n *\n * Note on Azure: Azure OpenAI also supports Microsoft Entra ID `Authorization:\n * Bearer <token>` OAuth. aimock only ever injects the static `api-key` header,\n * and a real Entra bearer token from the caller is never dummy-prefixed, so it\n * is always forwarded verbatim (the caller overrides aimock).\n */\nexport type AuthScheme =\n | { kind: \"bearer\" } // Authorization: Bearer <key>\n | { kind: \"fal-key\" } // Authorization: Key <key>\n | { kind: \"x-api-key\" } // x-api-key: <key>\n | { kind: \"x-goog-api-key\" } // x-goog-api-key: <key>\n | { kind: \"api-key\" } // api-key: <key>\n | { kind: \"xi-api-key\" }; // xi-api-key: <key>\n\nconst PROVIDER_AUTH_SCHEMES: Partial<Record<RecordProviderKey, AuthScheme>> = {\n // Bearer-token providers.\n openai: { kind: \"bearer\" },\n openrouter: { kind: \"bearer\" },\n cohere: { kind: \"bearer\" },\n grok: { kind: \"bearer\" }, // xAI\n ollama: { kind: \"bearer\" }, // Ollama Cloud / bearer-gated Ollama servers\n // Anthropic.\n anthropic: { kind: \"x-api-key\" },\n // Google (Gemini + Veo) use the x-goog-api-key header scheme.\n gemini: { kind: \"x-goog-api-key\" },\n \"gemini-interactions\": { kind: \"x-goog-api-key\" },\n veo: { kind: \"x-goog-api-key\" },\n // Azure OpenAI static-key auth.\n azure: { kind: \"api-key\" },\n // ElevenLabs.\n elevenlabs: { kind: \"xi-api-key\" },\n // fal.ai — uses the `Key ` prefix on Authorization, NOT `Bearer `.\n fal: { kind: \"fal-key\" },\n};\n\nexport function hasStaticProviderAuthScheme(providerKey: RecordProviderKey): boolean {\n return PROVIDER_AUTH_SCHEMES[providerKey] !== undefined;\n}\n\n/** Force a known provider credential onto an already-scrubbed outbound map. */\nexport function applyConfiguredProviderAuth(\n forwardHeaders: Record<string, string>,\n target: URL,\n providerKey: RecordProviderKey,\n providerCredential: string,\n): boolean {\n void target;\n const scheme = PROVIDER_AUTH_SCHEMES[providerKey];\n if (!scheme) return false;\n switch (scheme.kind) {\n case \"bearer\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders.Authorization = `Bearer ${providerCredential}`;\n break;\n case \"fal-key\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders.Authorization = `Key ${providerCredential}`;\n break;\n default:\n takeHeader(forwardHeaders, authHeaderName(scheme));\n forwardHeaders[authHeaderName(scheme)] = providerCredential;\n }\n return true;\n}\n\n/**\n * Case-insensitively delete a header from a forward-header map, returning the\n * value that was present (if any). Header maps preserve the original casing of\n * incoming headers, so we can't assume a canonical key.\n */\nfunction takeHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n let found: string | undefined;\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lowerName) {\n found = headers[key];\n delete headers[key];\n }\n }\n return found;\n}\n\n/**\n * Extract the caller's credential value for a given auth scheme from the\n * forwarded headers, if present. For bearer, strips the `Bearer ` prefix.\n */\nfunction readCallerCredential(\n headers: Record<string, string>,\n scheme: AuthScheme,\n): string | undefined {\n if (scheme.kind === \"bearer\" || scheme.kind === \"fal-key\") {\n const raw = scanHeader(headers, \"authorization\");\n if (raw === undefined) return undefined;\n // Strip the scheme prefix (`Bearer ` for bearer, `Key ` for fal) if present,\n // otherwise treat the whole value as the credential.\n const prefix = scheme.kind === \"fal-key\" ? \"Key\" : \"Bearer\";\n const match = new RegExp(`^\\\\s*${prefix}\\\\s+(.+)$`, \"i\").exec(raw);\n return match ? match[1].trim() : raw.trim();\n }\n return scanHeader(headers, authHeaderName(scheme));\n}\n\n/**\n * The wire header name a non-Authorization scheme carries its credential in.\n * (bearer/fal-key both use `Authorization` and are handled separately.)\n */\nfunction authHeaderName(scheme: AuthScheme): string {\n switch (scheme.kind) {\n case \"x-api-key\":\n return \"x-api-key\";\n case \"x-goog-api-key\":\n return \"x-goog-api-key\";\n case \"api-key\":\n return \"api-key\";\n case \"xi-api-key\":\n return \"xi-api-key\";\n case \"bearer\":\n case \"fal-key\":\n return \"authorization\";\n }\n}\n\n/** Case-insensitive header lookup that does not mutate the map. */\nfunction scanHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lowerName) return headers[key];\n }\n return undefined;\n}\n\n/**\n * Decide whether aimock should override the caller's credential with its own\n * built-in key. Override when the caller sent no credential OR sent a dummy\n * placeholder (prefixed with the active marker). A real caller key is left\n * untouched so the caller can always override aimock.\n */\nfunction shouldOverride(callerCredential: string | undefined, marker: string): boolean {\n if (callerCredential === undefined || callerCredential.length === 0) return true;\n return callerCredential.startsWith(marker);\n}\n\n/**\n * Inject aimock's built-in upstream credential into the forwarded headers when\n * appropriate (opt-in, backward-compatible). Mutates `forwardHeaders` in place\n * and may mutate `target` (query-param schemes; none currently).\n *\n * Precedence:\n * - No built-in key configured for this provider → no-op (forward verbatim).\n * - Provider not a static-key scheme → no-op (Bedrock SigV4 / Vertex AI OAuth).\n * - Caller credential absent or dummy-prefixed → inject built-in key.\n * - Caller credential is a real key → forward it unchanged.\n *\n * @param forwardHeaders headers already built by buildForwardHeaders(req)\n * @param target upstream URL (reserved for query-param auth schemes)\n * @param providerKey which provider this request is routed to\n * @param builtinKey aimock's configured key for this provider (if any)\n */\nexport function applyProviderAuth(\n forwardHeaders: Record<string, string>,\n target: URL,\n providerKey: RecordProviderKey,\n builtinKey: string | undefined,\n): void {\n void target; // reserved for future query-param schemes (e.g. Gemini ?key=)\n if (!builtinKey) return; // feature inert for this provider\n\n const scheme = PROVIDER_AUTH_SCHEMES[providerKey];\n if (!scheme) return; // signed/OAuth provider — never rewrite auth\n\n const marker = getDummyKeyMarker();\n const callerCredential = readCallerCredential(forwardHeaders, scheme);\n if (!shouldOverride(callerCredential, marker)) return; // caller sent a real key\n\n // Strip any existing auth headers for this scheme (across casings) before\n // setting aimock's key, so a dummy caller key can't linger alongside ours.\n switch (scheme.kind) {\n case \"bearer\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders[\"Authorization\"] = `Bearer ${builtinKey}`;\n break;\n case \"fal-key\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders[\"Authorization\"] = `Key ${builtinKey}`;\n break;\n case \"x-api-key\":\n takeHeader(forwardHeaders, \"x-api-key\");\n forwardHeaders[\"x-api-key\"] = builtinKey;\n break;\n case \"x-goog-api-key\":\n takeHeader(forwardHeaders, \"x-goog-api-key\");\n forwardHeaders[\"x-goog-api-key\"] = builtinKey;\n break;\n case \"api-key\":\n takeHeader(forwardHeaders, \"api-key\");\n forwardHeaders[\"api-key\"] = builtinKey;\n break;\n case \"xi-api-key\":\n takeHeader(forwardHeaders, \"xi-api-key\");\n forwardHeaders[\"xi-api-key\"] = builtinKey;\n break;\n }\n}\n\n/**\n * Read per-provider built-in keys from the environment. Returns undefined when\n * no provider key is configured so the feature stays fully inert by default.\n *\n * Each wired static-key provider reads `AIMOCK_PROVIDER_<PROVIDER>_KEY`. An\n * empty-string value is treated as unset (existing truthiness pattern), keeping\n * the feature inert. `gemini-interactions` is not read here: it reuses the\n * Gemini key via the lookup-key remap in `proxyAndRecord`. Signed/OAuth\n * providers (`bedrock`, `vertexai`) have no env var — aimock never rewrites\n * their auth.\n */\nexport function readProviderKeysFromEnv(\n env: NodeJS.ProcessEnv = process.env,\n): Partial<Record<RecordProviderKey, string>> | undefined {\n const keys: Partial<Record<RecordProviderKey, string>> = {};\n if (env.AIMOCK_PROVIDER_OPENAI_KEY) keys.openai = env.AIMOCK_PROVIDER_OPENAI_KEY;\n if (env.AIMOCK_PROVIDER_ANTHROPIC_KEY) keys.anthropic = env.AIMOCK_PROVIDER_ANTHROPIC_KEY;\n if (env.AIMOCK_PROVIDER_GEMINI_KEY) keys.gemini = env.AIMOCK_PROVIDER_GEMINI_KEY;\n if (env.AIMOCK_PROVIDER_OPENROUTER_KEY) keys.openrouter = env.AIMOCK_PROVIDER_OPENROUTER_KEY;\n if (env.AIMOCK_PROVIDER_COHERE_KEY) keys.cohere = env.AIMOCK_PROVIDER_COHERE_KEY;\n if (env.AIMOCK_PROVIDER_GROK_KEY) keys.grok = env.AIMOCK_PROVIDER_GROK_KEY;\n if (env.AIMOCK_PROVIDER_OLLAMA_KEY) keys.ollama = env.AIMOCK_PROVIDER_OLLAMA_KEY;\n if (env.AIMOCK_PROVIDER_VEO_KEY) keys.veo = env.AIMOCK_PROVIDER_VEO_KEY;\n if (env.AIMOCK_PROVIDER_AZURE_KEY) keys.azure = env.AIMOCK_PROVIDER_AZURE_KEY;\n if (env.AIMOCK_PROVIDER_ELEVENLABS_KEY) keys.elevenlabs = env.AIMOCK_PROVIDER_ELEVENLABS_KEY;\n if (env.AIMOCK_PROVIDER_FAL_KEY) keys.fal = env.AIMOCK_PROVIDER_FAL_KEY;\n return Object.keys(keys).length > 0 ? keys : undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"provider-auth.js","names":[],"sources":["../src/provider-auth.ts"],"sourcesContent":["import type { RecordProviderKey } from \"./types.js\";\n\n/**\n * Default marker prefix identifying a \"dummy\" credential that a caller sends\n * only to satisfy an SDK's non-empty API-key requirement (e.g. `sk-aimock-...`).\n * A caller credential that is absent OR begins with this prefix is treated as a\n * placeholder that aimock is free to override with its own built-in upstream key.\n * A caller credential that does NOT begin with this prefix is treated as a real\n * key and forwarded verbatim (the caller overrides aimock).\n *\n * Overridable via the `AIMOCK_DUMMY_KEY_MARKER` env var for setups that mint\n * placeholder keys under a different prefix.\n */\nexport const DEFAULT_DUMMY_KEY_MARKER = \"sk-aimock-\";\n\n/** Resolve the active dummy-key marker, honoring the env override. */\nexport function getDummyKeyMarker(): string {\n const override = process.env.AIMOCK_DUMMY_KEY_MARKER;\n return override && override.length > 0 ? override : DEFAULT_DUMMY_KEY_MARKER;\n}\n\n/**\n * How a given provider carries its API credential on the wire. aimock injects\n * its built-in key using the provider's native scheme so the upstream accepts\n * it. Providers whose auth is a signed/exchanged credential (Bedrock SigV4,\n * Vertex AI OAuth) are deliberately absent — those are NOT simple static-key\n * schemes, so aimock never rewrites their auth and always forwards the caller's\n * credential unchanged.\n *\n * Scheme kinds (all static long-lived keys):\n * - bearer `Authorization: Bearer <key>` (OpenAI, OpenRouter, Cohere, Grok/xAI, BytePlus Ark, Ollama)\n * - fal-key `Authorization: Key <key>` (fal.ai — note the `Key ` prefix, NOT `Bearer`)\n * - x-api-key `x-api-key: <key>` (Anthropic)\n * - x-goog-api-key `x-goog-api-key: <key>` (Gemini, Gemini Interactions, Veo)\n * - api-key `api-key: <key>` (Azure OpenAI static-key auth)\n * - xi-api-key `xi-api-key: <key>` (ElevenLabs)\n *\n * Note on Azure: Azure OpenAI also supports Microsoft Entra ID `Authorization:\n * Bearer <token>` OAuth. aimock only ever injects the static `api-key` header,\n * and a real Entra bearer token from the caller is never dummy-prefixed, so it\n * is always forwarded verbatim (the caller overrides aimock).\n */\nexport type AuthScheme =\n | { kind: \"bearer\" } // Authorization: Bearer <key>\n | { kind: \"fal-key\" } // Authorization: Key <key>\n | { kind: \"x-api-key\" } // x-api-key: <key>\n | { kind: \"x-goog-api-key\" } // x-goog-api-key: <key>\n | { kind: \"api-key\" } // api-key: <key>\n | { kind: \"xi-api-key\" }; // xi-api-key: <key>\n\nconst PROVIDER_AUTH_SCHEMES: Partial<Record<RecordProviderKey, AuthScheme>> = {\n // Bearer-token providers.\n openai: { kind: \"bearer\" },\n openrouter: { kind: \"bearer\" },\n cohere: { kind: \"bearer\" },\n grok: { kind: \"bearer\" }, // xAI\n byteplus: { kind: \"bearer\" }, // BytePlus Ark (ModelArk) — Authorization: Bearer $ARK_API_KEY\n ollama: { kind: \"bearer\" }, // Ollama Cloud / bearer-gated Ollama servers\n // Anthropic.\n anthropic: { kind: \"x-api-key\" },\n // Google (Gemini + Veo) use the x-goog-api-key header scheme.\n gemini: { kind: \"x-goog-api-key\" },\n \"gemini-interactions\": { kind: \"x-goog-api-key\" },\n veo: { kind: \"x-goog-api-key\" },\n // Azure OpenAI static-key auth.\n azure: { kind: \"api-key\" },\n // ElevenLabs.\n elevenlabs: { kind: \"xi-api-key\" },\n // fal.ai — uses the `Key ` prefix on Authorization, NOT `Bearer `.\n fal: { kind: \"fal-key\" },\n};\n\nexport function hasStaticProviderAuthScheme(providerKey: RecordProviderKey): boolean {\n return PROVIDER_AUTH_SCHEMES[providerKey] !== undefined;\n}\n\n/** Force a known provider credential onto an already-scrubbed outbound map. */\nexport function applyConfiguredProviderAuth(\n forwardHeaders: Record<string, string>,\n target: URL,\n providerKey: RecordProviderKey,\n providerCredential: string,\n): boolean {\n void target;\n const scheme = PROVIDER_AUTH_SCHEMES[providerKey];\n if (!scheme) return false;\n switch (scheme.kind) {\n case \"bearer\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders.Authorization = `Bearer ${providerCredential}`;\n break;\n case \"fal-key\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders.Authorization = `Key ${providerCredential}`;\n break;\n default:\n takeHeader(forwardHeaders, authHeaderName(scheme));\n forwardHeaders[authHeaderName(scheme)] = providerCredential;\n }\n return true;\n}\n\n/**\n * Case-insensitively delete a header from a forward-header map, returning the\n * value that was present (if any). Header maps preserve the original casing of\n * incoming headers, so we can't assume a canonical key.\n */\nfunction takeHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n let found: string | undefined;\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lowerName) {\n found = headers[key];\n delete headers[key];\n }\n }\n return found;\n}\n\n/**\n * Extract the caller's credential value for a given auth scheme from the\n * forwarded headers, if present. For bearer, strips the `Bearer ` prefix.\n */\nfunction readCallerCredential(\n headers: Record<string, string>,\n scheme: AuthScheme,\n): string | undefined {\n if (scheme.kind === \"bearer\" || scheme.kind === \"fal-key\") {\n const raw = scanHeader(headers, \"authorization\");\n if (raw === undefined) return undefined;\n // Strip the scheme prefix (`Bearer ` for bearer, `Key ` for fal) if present,\n // otherwise treat the whole value as the credential.\n const prefix = scheme.kind === \"fal-key\" ? \"Key\" : \"Bearer\";\n const match = new RegExp(`^\\\\s*${prefix}\\\\s+(.+)$`, \"i\").exec(raw);\n return match ? match[1].trim() : raw.trim();\n }\n return scanHeader(headers, authHeaderName(scheme));\n}\n\n/**\n * The wire header name a non-Authorization scheme carries its credential in.\n * (bearer/fal-key both use `Authorization` and are handled separately.)\n */\nfunction authHeaderName(scheme: AuthScheme): string {\n switch (scheme.kind) {\n case \"x-api-key\":\n return \"x-api-key\";\n case \"x-goog-api-key\":\n return \"x-goog-api-key\";\n case \"api-key\":\n return \"api-key\";\n case \"xi-api-key\":\n return \"xi-api-key\";\n case \"bearer\":\n case \"fal-key\":\n return \"authorization\";\n }\n}\n\n/** Case-insensitive header lookup that does not mutate the map. */\nfunction scanHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n for (const key of Object.keys(headers)) {\n if (key.toLowerCase() === lowerName) return headers[key];\n }\n return undefined;\n}\n\n/**\n * Decide whether aimock should override the caller's credential with its own\n * built-in key. Override when the caller sent no credential OR sent a dummy\n * placeholder (prefixed with the active marker). A real caller key is left\n * untouched so the caller can always override aimock.\n */\nfunction shouldOverride(callerCredential: string | undefined, marker: string): boolean {\n if (callerCredential === undefined || callerCredential.length === 0) return true;\n return callerCredential.startsWith(marker);\n}\n\n/**\n * Inject aimock's built-in upstream credential into the forwarded headers when\n * appropriate (opt-in, backward-compatible). Mutates `forwardHeaders` in place\n * and may mutate `target` (query-param schemes; none currently).\n *\n * Precedence:\n * - No built-in key configured for this provider → no-op (forward verbatim).\n * - Provider not a static-key scheme → no-op (Bedrock SigV4 / Vertex AI OAuth).\n * - Caller credential absent or dummy-prefixed → inject built-in key.\n * - Caller credential is a real key → forward it unchanged.\n *\n * @param forwardHeaders headers already built by buildForwardHeaders(req)\n * @param target upstream URL (reserved for query-param auth schemes)\n * @param providerKey which provider this request is routed to\n * @param builtinKey aimock's configured key for this provider (if any)\n */\nexport function applyProviderAuth(\n forwardHeaders: Record<string, string>,\n target: URL,\n providerKey: RecordProviderKey,\n builtinKey: string | undefined,\n): void {\n void target; // reserved for future query-param schemes (e.g. Gemini ?key=)\n if (!builtinKey) return; // feature inert for this provider\n\n const scheme = PROVIDER_AUTH_SCHEMES[providerKey];\n if (!scheme) return; // signed/OAuth provider — never rewrite auth\n\n const marker = getDummyKeyMarker();\n const callerCredential = readCallerCredential(forwardHeaders, scheme);\n if (!shouldOverride(callerCredential, marker)) return; // caller sent a real key\n\n // Strip any existing auth headers for this scheme (across casings) before\n // setting aimock's key, so a dummy caller key can't linger alongside ours.\n switch (scheme.kind) {\n case \"bearer\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders[\"Authorization\"] = `Bearer ${builtinKey}`;\n break;\n case \"fal-key\":\n takeHeader(forwardHeaders, \"authorization\");\n forwardHeaders[\"Authorization\"] = `Key ${builtinKey}`;\n break;\n case \"x-api-key\":\n takeHeader(forwardHeaders, \"x-api-key\");\n forwardHeaders[\"x-api-key\"] = builtinKey;\n break;\n case \"x-goog-api-key\":\n takeHeader(forwardHeaders, \"x-goog-api-key\");\n forwardHeaders[\"x-goog-api-key\"] = builtinKey;\n break;\n case \"api-key\":\n takeHeader(forwardHeaders, \"api-key\");\n forwardHeaders[\"api-key\"] = builtinKey;\n break;\n case \"xi-api-key\":\n takeHeader(forwardHeaders, \"xi-api-key\");\n forwardHeaders[\"xi-api-key\"] = builtinKey;\n break;\n }\n}\n\n/**\n * Read per-provider built-in keys from the environment. Returns undefined when\n * no provider key is configured so the feature stays fully inert by default.\n *\n * Each wired static-key provider reads `AIMOCK_PROVIDER_<PROVIDER>_KEY`. An\n * empty-string value is treated as unset (existing truthiness pattern), keeping\n * the feature inert. `gemini-interactions` is not read here: it reuses the\n * Gemini key via the lookup-key remap in `proxyAndRecord`. Signed/OAuth\n * providers (`bedrock`, `vertexai`) have no env var — aimock never rewrites\n * their auth.\n */\nexport function readProviderKeysFromEnv(\n env: NodeJS.ProcessEnv = process.env,\n): Partial<Record<RecordProviderKey, string>> | undefined {\n const keys: Partial<Record<RecordProviderKey, string>> = {};\n if (env.AIMOCK_PROVIDER_OPENAI_KEY) keys.openai = env.AIMOCK_PROVIDER_OPENAI_KEY;\n if (env.AIMOCK_PROVIDER_ANTHROPIC_KEY) keys.anthropic = env.AIMOCK_PROVIDER_ANTHROPIC_KEY;\n if (env.AIMOCK_PROVIDER_GEMINI_KEY) keys.gemini = env.AIMOCK_PROVIDER_GEMINI_KEY;\n if (env.AIMOCK_PROVIDER_OPENROUTER_KEY) keys.openrouter = env.AIMOCK_PROVIDER_OPENROUTER_KEY;\n if (env.AIMOCK_PROVIDER_COHERE_KEY) keys.cohere = env.AIMOCK_PROVIDER_COHERE_KEY;\n if (env.AIMOCK_PROVIDER_GROK_KEY) keys.grok = env.AIMOCK_PROVIDER_GROK_KEY;\n if (env.AIMOCK_PROVIDER_BYTEPLUS_KEY) keys.byteplus = env.AIMOCK_PROVIDER_BYTEPLUS_KEY;\n if (env.AIMOCK_PROVIDER_OLLAMA_KEY) keys.ollama = env.AIMOCK_PROVIDER_OLLAMA_KEY;\n if (env.AIMOCK_PROVIDER_VEO_KEY) keys.veo = env.AIMOCK_PROVIDER_VEO_KEY;\n if (env.AIMOCK_PROVIDER_AZURE_KEY) keys.azure = env.AIMOCK_PROVIDER_AZURE_KEY;\n if (env.AIMOCK_PROVIDER_ELEVENLABS_KEY) keys.elevenlabs = env.AIMOCK_PROVIDER_ELEVENLABS_KEY;\n if (env.AIMOCK_PROVIDER_FAL_KEY) keys.fal = env.AIMOCK_PROVIDER_FAL_KEY;\n return Object.keys(keys).length > 0 ? keys : undefined;\n}\n"],"mappings":"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"}
package/dist/rerank.cjs CHANGED
@@ -28,7 +28,46 @@ async function handleRerank(req, res, raw, fixtures, journal, defaults, setCorsH
28
28
  } }));
29
29
  return;
30
30
  }
31
- const query = body.query ?? "";
31
+ if (!require_helpers.isJsonObject(body)) {
32
+ journal.add({
33
+ method: req.method ?? "POST",
34
+ path: req.url ?? "/v2/rerank",
35
+ headers: require_helpers.flattenHeaders(req.headers),
36
+ body: null,
37
+ service: "rerank",
38
+ response: {
39
+ status: 400,
40
+ fixture: null
41
+ }
42
+ });
43
+ res.writeHead(400, { "Content-Type": "application/json" });
44
+ res.end(JSON.stringify({ error: {
45
+ message: "Request body must be a JSON object",
46
+ type: "invalid_request_error"
47
+ } }));
48
+ return;
49
+ }
50
+ const normalizedQuery = require_helpers.normalizeTextInput(body.query ?? "");
51
+ if (normalizedQuery === null) {
52
+ journal.add({
53
+ method: req.method ?? "POST",
54
+ path: req.url ?? "/v2/rerank",
55
+ headers: require_helpers.flattenHeaders(req.headers),
56
+ body: null,
57
+ service: "rerank",
58
+ response: {
59
+ status: 400,
60
+ fixture: null
61
+ }
62
+ });
63
+ res.writeHead(400, { "Content-Type": "application/json" });
64
+ res.end(JSON.stringify({ error: {
65
+ message: "Invalid parameter: 'query' must be a string or an array of strings",
66
+ type: "invalid_request_error"
67
+ } }));
68
+ return;
69
+ }
70
+ const query = normalizedQuery;
32
71
  let matchedResults = [];
33
72
  let matchedFixture = null;
34
73
  for (const fixture of fixtures) if (require_helpers.matchesPattern(query, fixture.match)) {
@@ -38,6 +77,29 @@ async function handleRerank(req, res, raw, fixtures, journal, defaults, setCorsH
38
77
  }
39
78
  if (matchedFixture) logger.debug(`Rerank fixture matched for query "${query.slice(0, 80)}"`);
40
79
  else logger.debug(`No rerank fixture matched for query "${query.slice(0, 80)}" — returning empty`);
80
+ if (!matchedFixture && require_helpers.resolveStrictMode(defaults.strict, req.headers)) {
81
+ const strictMessage = require_helpers.strictNoMatchMessage(0);
82
+ logger.error(require_helpers.strictNoMatchLogLine(req.method ?? "POST", req.url ?? "/v2/rerank", 0));
83
+ journal.add({
84
+ method: req.method ?? "POST",
85
+ path: req.url ?? "/v2/rerank",
86
+ headers: require_helpers.flattenHeaders(req.headers),
87
+ body: null,
88
+ service: "rerank",
89
+ response: {
90
+ status: 503,
91
+ fixture: null,
92
+ ...require_helpers.strictOverrideField(defaults.strict, req.headers)
93
+ }
94
+ });
95
+ res.writeHead(503, { "Content-Type": "application/json" });
96
+ res.end(JSON.stringify({ error: {
97
+ message: strictMessage,
98
+ type: "invalid_request_error",
99
+ code: "no_fixture_match"
100
+ } }));
101
+ return;
102
+ }
41
103
  const results = matchedResults.map((r) => ({
42
104
  index: r.index,
43
105
  relevance_score: r.relevance_score
@@ -50,7 +112,8 @@ async function handleRerank(req, res, raw, fixtures, journal, defaults, setCorsH
50
112
  service: "rerank",
51
113
  response: {
52
114
  status: 200,
53
- fixture: null
115
+ fixture: null,
116
+ ...require_helpers.strictOverrideField(defaults.strict, req.headers)
54
117
  }
55
118
  });
56
119
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -1 +1 @@
1
- {"version":3,"file":"rerank.cjs","names":["flattenHeaders","matchesPattern","generateId"],"sources":["../src/rerank.ts"],"sourcesContent":["/**\n * Reranking API support for LLMock.\n *\n * Handles POST /v2/rerank requests (Cohere rerank-compatible). Matches\n * fixtures by comparing the request `query` field against registered\n * patterns. First match wins; no match returns empty results.\n */\n\nimport type * as http from \"node:http\";\nimport { flattenHeaders, generateId, matchesPattern } from \"./helpers.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\n\n// ─── Rerank types ─────────────────────────────────────────────────────────\n\nexport interface RerankResult {\n index: number;\n relevance_score: number;\n}\n\nexport interface RerankFixture {\n match: string | RegExp;\n results: RerankResult[];\n}\n\n// ─── Request handler ──────────────────────────────────────────────────────\n\nexport async function handleRerank(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: RerankFixture[],\n journal: Journal,\n defaults: { logger: Logger },\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let body: { query?: string; model?: string };\n try {\n body = JSON.parse(raw) as { query?: string; model?: string };\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/rerank\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"rerank\",\n response: { status: 400, fixture: null },\n });\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: `Malformed JSON: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n\n const query = body.query ?? \"\";\n\n // Find first matching fixture\n let matchedResults: RerankResult[] = [];\n let matchedFixture: RerankFixture | null = null;\n\n for (const fixture of fixtures) {\n if (matchesPattern(query, fixture.match)) {\n matchedFixture = fixture;\n matchedResults = fixture.results;\n break;\n }\n }\n\n if (matchedFixture) {\n logger.debug(`Rerank fixture matched for query \"${query.slice(0, 80)}\"`);\n } else {\n logger.debug(`No rerank fixture matched for query \"${query.slice(0, 80)}\" — returning empty`);\n }\n\n // Build response (Cohere rerank v2 format: index + relevance_score only)\n const results = matchedResults.map((r) => ({\n index: r.index,\n relevance_score: r.relevance_score,\n }));\n\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/rerank\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"rerank\",\n response: { status: 200, fixture: null },\n });\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n id: generateId(\"rerank\"),\n results,\n meta: {\n billed_units: { search_units: 0 },\n },\n }),\n );\n}\n"],"mappings":";;;AA2BA,eAAsB,aACpB,KACA,KACA,KACA,UACA,SACA,UACA,gBACe;CACf,MAAM,EAAE,WAAW;AACnB,gBAAe,IAAI;CAEnB,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,IAAI;UACf,UAAU;EACjB,MAAM,SAAS,oBAAoB,QAAQ,SAAS,UAAU;AAC9D,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,SAAS;GACT,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IACF,KAAK,UAAU,EACb,OAAO;GACL,SAAS,mBAAmB;GAC5B,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAGF,MAAM,QAAQ,KAAK,SAAS;CAG5B,IAAI,iBAAiC,EAAE;CACvC,IAAI,iBAAuC;AAE3C,MAAK,MAAM,WAAW,SACpB,KAAIC,+BAAe,OAAO,QAAQ,MAAM,EAAE;AACxC,mBAAiB;AACjB,mBAAiB,QAAQ;AACzB;;AAIJ,KAAI,eACF,QAAO,MAAM,qCAAqC,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG;KAExE,QAAO,MAAM,wCAAwC,MAAM,MAAM,GAAG,GAAG,CAAC,qBAAqB;CAI/F,MAAM,UAAU,eAAe,KAAK,OAAO;EACzC,OAAO,EAAE;EACT,iBAAiB,EAAE;EACpB,EAAE;AAEH,SAAQ,IAAI;EACV,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAASD,+BAAe,IAAI,QAAQ;EACpC,MAAM;EACN,SAAS;EACT,UAAU;GAAE,QAAQ;GAAK,SAAS;GAAM;EACzC,CAAC;AAEF,KAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,KAAI,IACF,KAAK,UAAU;EACb,IAAIE,2BAAW,SAAS;EACxB;EACA,MAAM,EACJ,cAAc,EAAE,cAAc,GAAG,EAClC;EACF,CAAC,CACH"}
1
+ {"version":3,"file":"rerank.cjs","names":["flattenHeaders","isJsonObject","normalizeTextInput","matchesPattern","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","generateId"],"sources":["../src/rerank.ts"],"sourcesContent":["/**\n * Reranking API support for LLMock.\n *\n * Handles POST /v2/rerank requests (Cohere rerank-compatible). Matches\n * fixtures by comparing the request `query` field against registered\n * patterns. First match wins; no match returns empty results.\n */\n\nimport type * as http from \"node:http\";\nimport {\n flattenHeaders,\n generateId,\n isJsonObject,\n matchesPattern,\n normalizeTextInput,\n resolveStrictMode,\n strictNoMatchLogLine,\n strictNoMatchMessage,\n strictOverrideField,\n} from \"./helpers.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\n\n// ─── Rerank types ─────────────────────────────────────────────────────────\n\nexport interface RerankResult {\n index: number;\n relevance_score: number;\n}\n\nexport interface RerankFixture {\n match: string | RegExp;\n results: RerankResult[];\n}\n\n// ─── Request handler ──────────────────────────────────────────────────────\n\nexport async function handleRerank(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: RerankFixture[],\n journal: Journal,\n defaults: { logger: Logger; strict?: boolean },\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let body: { query?: unknown; model?: string };\n try {\n body = JSON.parse(raw) as { query?: unknown; model?: string };\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/rerank\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"rerank\",\n response: { status: 400, fixture: null },\n });\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: `Malformed JSON: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n\n // Reject bodies that parsed but are not a JSON object (e.g. `null`) before\n // touching fields — otherwise `body.query` throws a TypeError that surfaces\n // as a 500 instead of a 400.\n if (!isJsonObject(body)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/rerank\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"rerank\",\n response: { status: 400, fixture: null },\n });\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: \"Request body must be a JSON object\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const rawQuery: unknown = body.query ?? \"\";\n const normalizedQuery = normalizeTextInput(rawQuery);\n if (normalizedQuery === null) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/rerank\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"rerank\",\n response: { status: 400, fixture: null },\n });\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: \"Invalid parameter: 'query' must be a string or an array of strings\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n const query = normalizedQuery;\n\n // Find first matching fixture\n let matchedResults: RerankResult[] = [];\n let matchedFixture: RerankFixture | null = null;\n\n for (const fixture of fixtures) {\n if (matchesPattern(query, fixture.match)) {\n matchedFixture = fixture;\n matchedResults = fixture.results;\n break;\n }\n }\n\n if (matchedFixture) {\n logger.debug(`Rerank fixture matched for query \"${query.slice(0, 80)}\"`);\n } else {\n logger.debug(`No rerank fixture matched for query \"${query.slice(0, 80)}\" — returning empty`);\n }\n\n // Strict mode turns the lenient default (empty results) into a no-match\n // error, like every other fixture-matching handler. These fixtures carry no\n // sequence/turn state, so the skipped count is always 0.\n if (!matchedFixture && resolveStrictMode(defaults.strict, req.headers)) {\n const strictMessage = strictNoMatchMessage(0);\n logger.error(strictNoMatchLogLine(req.method ?? \"POST\", req.url ?? \"/v2/rerank\", 0));\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/rerank\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"rerank\",\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n res.writeHead(503, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n // Build response (Cohere rerank v2 format: index + relevance_score only)\n const results = matchedResults.map((r) => ({\n index: r.index,\n relevance_score: r.relevance_score,\n }));\n\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/rerank\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"rerank\",\n response: {\n status: 200,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n id: generateId(\"rerank\"),\n results,\n meta: {\n billed_units: { search_units: 0 },\n },\n }),\n );\n}\n"],"mappings":";;;AAqCA,eAAsB,aACpB,KACA,KACA,KACA,UACA,SACA,UACA,gBACe;CACf,MAAM,EAAE,WAAW;AACnB,gBAAe,IAAI;CAEnB,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,IAAI;UACf,UAAU;EACjB,MAAM,SAAS,oBAAoB,QAAQ,SAAS,UAAU;AAC9D,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASA,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,SAAS;GACT,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IACF,KAAK,UAAU,EACb,OAAO;GACL,SAAS,mBAAmB;GAC5B,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAMF,KAAI,CAACC,6BAAa,KAAK,EAAE;AACvB,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASD,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,SAAS;GACT,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IACF,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAIF,MAAM,kBAAkBE,mCADE,KAAK,SAAS,GACY;AACpD,KAAI,oBAAoB,MAAM;AAC5B,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASF,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,SAAS;GACT,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IACF,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAEF,MAAM,QAAQ;CAGd,IAAI,iBAAiC,EAAE;CACvC,IAAI,iBAAuC;AAE3C,MAAK,MAAM,WAAW,SACpB,KAAIG,+BAAe,OAAO,QAAQ,MAAM,EAAE;AACxC,mBAAiB;AACjB,mBAAiB,QAAQ;AACzB;;AAIJ,KAAI,eACF,QAAO,MAAM,qCAAqC,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG;KAExE,QAAO,MAAM,wCAAwC,MAAM,MAAM,GAAG,GAAG,CAAC,qBAAqB;AAM/F,KAAI,CAAC,kBAAkBC,kCAAkB,SAAS,QAAQ,IAAI,QAAQ,EAAE;EACtE,MAAM,gBAAgBC,qCAAqB,EAAE;AAC7C,SAAO,MAAMC,qCAAqB,IAAI,UAAU,QAAQ,IAAI,OAAO,cAAc,EAAE,CAAC;AACpF,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASN,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,SAAS;GACT,UAAU;IACR,QAAQ;IACR,SAAS;IACT,GAAGO,oCAAoB,SAAS,QAAQ,IAAI,QAAQ;IACrD;GACF,CAAC;AACF,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IACF,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAIF,MAAM,UAAU,eAAe,KAAK,OAAO;EACzC,OAAO,EAAE;EACT,iBAAiB,EAAE;EACpB,EAAE;AAEH,SAAQ,IAAI;EACV,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAASP,+BAAe,IAAI,QAAQ;EACpC,MAAM;EACN,SAAS;EACT,UAAU;GACR,QAAQ;GACR,SAAS;GACT,GAAGO,oCAAoB,SAAS,QAAQ,IAAI,QAAQ;GACrD;EACF,CAAC;AAEF,KAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,KAAI,IACF,KAAK,UAAU;EACb,IAAIC,2BAAW,SAAS;EACxB;EACA,MAAM,EACJ,cAAc,EAAE,cAAc,GAAG,EAClC;EACF,CAAC,CACH"}
package/dist/rerank.d.cts CHANGED
@@ -14,9 +14,9 @@ interface RerankFixture {
14
14
  }
15
15
  declare function handleRerank(req: http$1.IncomingMessage, res: http$1.ServerResponse, raw: string, fixtures: RerankFixture[], journal: Journal, defaults: {
16
16
  logger: Logger;
17
+ strict?: boolean;
17
18
  }, setCorsHeaders: (res: http$1.ServerResponse) => void): Promise<void>;
18
19
  //# sourceMappingURL=rerank.d.ts.map
19
-
20
20
  //#endregion
21
21
  export { RerankFixture, RerankResult, handleRerank };
22
22
  //# sourceMappingURL=rerank.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rerank.d.cts","names":[],"sources":["../src/rerank.ts"],"sourcesContent":[],"mappings":";;;;;;AAsBuB,UAPN,YAAA,CAOM;EAKD,KAAA,EAAA,MAAA;EAAY,eAAA,EAAA,MAAA;;AAE3B,UATU,aAAA,CASL;OAEA,EAAA,MAAA,GAVM,MAUN;SACD,EAVA,YAUA,EAAA;;AAEa,iBAPF,YAAA,CAOO,GAAA,EANtB,MAAA,CAAK,eAMiB,EAAA,GAAA,EALtB,MAAA,CAAK,cAKiB,EAAA,GAAA,EAAA,MAAA,EAAA,QAAA,EAHjB,aAGiB,EAAA,EAAA,OAAA,EAFlB,OAEkB,EAAA,QAAA,EAAA;QAC1B,EAFmB,MAEnB;CAAO,EAAA,cAAA,EAAA,CAAA,GAAA,EADc,MAAA,CAAK,cACnB,EAAA,GAAA,IAAA,CAAA,EAAP,OAAO,CAAA,IAAA,CAAA"}
1
+ {"version":3,"file":"rerank.d.cts","names":[],"sources":["../src/rerank.ts"],"sourcesContent":[],"mappings":";;;;;;AAgCuB,UAPN,YAAA,CAOM;EAKD,KAAA,EAAA,MAAA;EAAY,eAAA,EAAA,MAAA;;AAE3B,UATU,aAAA,CASL;OAEA,EAAA,MAAA,GAVM,MAUN;SACD,EAVA,YAUA,EAAA;;AAEa,iBAPF,YAAA,CAOO,GAAA,EANtB,MAAA,CAAK,eAMiB,EAAA,GAAA,EALtB,MAAA,CAAK,cAKiB,EAAA,GAAA,EAAA,MAAA,EAAA,QAAA,EAHjB,aAGiB,EAAA,EAAA,OAAA,EAFlB,OAEkB,EAAA,QAAA,EAAA;QAC1B,EAFmB,MAEnB;EAAO,MAAA,CAAA,EAAA,OAAA;yBADc,MAAA,CAAK,0BAC1B"}
package/dist/rerank.d.ts CHANGED
@@ -14,9 +14,9 @@ interface RerankFixture {
14
14
  }
15
15
  declare function handleRerank(req: http$1.IncomingMessage, res: http$1.ServerResponse, raw: string, fixtures: RerankFixture[], journal: Journal, defaults: {
16
16
  logger: Logger;
17
+ strict?: boolean;
17
18
  }, setCorsHeaders: (res: http$1.ServerResponse) => void): Promise<void>;
18
19
  //# sourceMappingURL=rerank.d.ts.map
19
-
20
20
  //#endregion
21
21
  export { RerankFixture, RerankResult, handleRerank };
22
22
  //# sourceMappingURL=rerank.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rerank.d.ts","names":[],"sources":["../src/rerank.ts"],"sourcesContent":[],"mappings":";;;;;;AAsBuB,UAPN,YAAA,CAOM;EAKD,KAAA,EAAA,MAAA;EAAY,eAAA,EAAA,MAAA;;AAE3B,UATU,aAAA,CASL;OAEA,EAAA,MAAA,GAVM,MAUN;SACD,EAVA,YAUA,EAAA;;AAEa,iBAPF,YAAA,CAOO,GAAA,EANtB,MAAA,CAAK,eAMiB,EAAA,GAAA,EALtB,MAAA,CAAK,cAKiB,EAAA,GAAA,EAAA,MAAA,EAAA,QAAA,EAHjB,aAGiB,EAAA,EAAA,OAAA,EAFlB,OAEkB,EAAA,QAAA,EAAA;QAC1B,EAFmB,MAEnB;CAAO,EAAA,cAAA,EAAA,CAAA,GAAA,EADc,MAAA,CAAK,cACnB,EAAA,GAAA,IAAA,CAAA,EAAP,OAAO,CAAA,IAAA,CAAA"}
1
+ {"version":3,"file":"rerank.d.ts","names":[],"sources":["../src/rerank.ts"],"sourcesContent":[],"mappings":";;;;;;AAgCuB,UAPN,YAAA,CAOM;EAKD,KAAA,EAAA,MAAA;EAAY,eAAA,EAAA,MAAA;;AAE3B,UATU,aAAA,CASL;OAEA,EAAA,MAAA,GAVM,MAUN;SACD,EAVA,YAUA,EAAA;;AAEa,iBAPF,YAAA,CAOO,GAAA,EANtB,MAAA,CAAK,eAMiB,EAAA,GAAA,EALtB,MAAA,CAAK,cAKiB,EAAA,GAAA,EAAA,MAAA,EAAA,QAAA,EAHjB,aAGiB,EAAA,EAAA,OAAA,EAFlB,OAEkB,EAAA,QAAA,EAAA;QAC1B,EAFmB,MAEnB;EAAO,MAAA,CAAA,EAAA,OAAA;yBADc,MAAA,CAAK,0BAC1B"}
package/dist/rerank.js CHANGED
@@ -1,4 +1,4 @@
1
- import { flattenHeaders, generateId, matchesPattern } from "./helpers.js";
1
+ import { flattenHeaders, generateId, isJsonObject, matchesPattern, normalizeTextInput, resolveStrictMode, strictNoMatchLogLine, strictNoMatchMessage, strictOverrideField } from "./helpers.js";
2
2
 
3
3
  //#region src/rerank.ts
4
4
  async function handleRerank(req, res, raw, fixtures, journal, defaults, setCorsHeaders) {
@@ -28,7 +28,46 @@ async function handleRerank(req, res, raw, fixtures, journal, defaults, setCorsH
28
28
  } }));
29
29
  return;
30
30
  }
31
- const query = body.query ?? "";
31
+ if (!isJsonObject(body)) {
32
+ journal.add({
33
+ method: req.method ?? "POST",
34
+ path: req.url ?? "/v2/rerank",
35
+ headers: flattenHeaders(req.headers),
36
+ body: null,
37
+ service: "rerank",
38
+ response: {
39
+ status: 400,
40
+ fixture: null
41
+ }
42
+ });
43
+ res.writeHead(400, { "Content-Type": "application/json" });
44
+ res.end(JSON.stringify({ error: {
45
+ message: "Request body must be a JSON object",
46
+ type: "invalid_request_error"
47
+ } }));
48
+ return;
49
+ }
50
+ const normalizedQuery = normalizeTextInput(body.query ?? "");
51
+ if (normalizedQuery === null) {
52
+ journal.add({
53
+ method: req.method ?? "POST",
54
+ path: req.url ?? "/v2/rerank",
55
+ headers: flattenHeaders(req.headers),
56
+ body: null,
57
+ service: "rerank",
58
+ response: {
59
+ status: 400,
60
+ fixture: null
61
+ }
62
+ });
63
+ res.writeHead(400, { "Content-Type": "application/json" });
64
+ res.end(JSON.stringify({ error: {
65
+ message: "Invalid parameter: 'query' must be a string or an array of strings",
66
+ type: "invalid_request_error"
67
+ } }));
68
+ return;
69
+ }
70
+ const query = normalizedQuery;
32
71
  let matchedResults = [];
33
72
  let matchedFixture = null;
34
73
  for (const fixture of fixtures) if (matchesPattern(query, fixture.match)) {
@@ -38,6 +77,29 @@ async function handleRerank(req, res, raw, fixtures, journal, defaults, setCorsH
38
77
  }
39
78
  if (matchedFixture) logger.debug(`Rerank fixture matched for query "${query.slice(0, 80)}"`);
40
79
  else logger.debug(`No rerank fixture matched for query "${query.slice(0, 80)}" — returning empty`);
80
+ if (!matchedFixture && resolveStrictMode(defaults.strict, req.headers)) {
81
+ const strictMessage = strictNoMatchMessage(0);
82
+ logger.error(strictNoMatchLogLine(req.method ?? "POST", req.url ?? "/v2/rerank", 0));
83
+ journal.add({
84
+ method: req.method ?? "POST",
85
+ path: req.url ?? "/v2/rerank",
86
+ headers: flattenHeaders(req.headers),
87
+ body: null,
88
+ service: "rerank",
89
+ response: {
90
+ status: 503,
91
+ fixture: null,
92
+ ...strictOverrideField(defaults.strict, req.headers)
93
+ }
94
+ });
95
+ res.writeHead(503, { "Content-Type": "application/json" });
96
+ res.end(JSON.stringify({ error: {
97
+ message: strictMessage,
98
+ type: "invalid_request_error",
99
+ code: "no_fixture_match"
100
+ } }));
101
+ return;
102
+ }
41
103
  const results = matchedResults.map((r) => ({
42
104
  index: r.index,
43
105
  relevance_score: r.relevance_score
@@ -50,7 +112,8 @@ async function handleRerank(req, res, raw, fixtures, journal, defaults, setCorsH
50
112
  service: "rerank",
51
113
  response: {
52
114
  status: 200,
53
- fixture: null
115
+ fixture: null,
116
+ ...strictOverrideField(defaults.strict, req.headers)
54
117
  }
55
118
  });
56
119
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -1 +1 @@
1
- {"version":3,"file":"rerank.js","names":[],"sources":["../src/rerank.ts"],"sourcesContent":["/**\n * Reranking API support for LLMock.\n *\n * Handles POST /v2/rerank requests (Cohere rerank-compatible). Matches\n * fixtures by comparing the request `query` field against registered\n * patterns. First match wins; no match returns empty results.\n */\n\nimport type * as http from \"node:http\";\nimport { flattenHeaders, generateId, matchesPattern } from \"./helpers.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\n\n// ─── Rerank types ─────────────────────────────────────────────────────────\n\nexport interface RerankResult {\n index: number;\n relevance_score: number;\n}\n\nexport interface RerankFixture {\n match: string | RegExp;\n results: RerankResult[];\n}\n\n// ─── Request handler ──────────────────────────────────────────────────────\n\nexport async function handleRerank(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: RerankFixture[],\n journal: Journal,\n defaults: { logger: Logger },\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let body: { query?: string; model?: string };\n try {\n body = JSON.parse(raw) as { query?: string; model?: string };\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/rerank\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"rerank\",\n response: { status: 400, fixture: null },\n });\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: `Malformed JSON: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n\n const query = body.query ?? \"\";\n\n // Find first matching fixture\n let matchedResults: RerankResult[] = [];\n let matchedFixture: RerankFixture | null = null;\n\n for (const fixture of fixtures) {\n if (matchesPattern(query, fixture.match)) {\n matchedFixture = fixture;\n matchedResults = fixture.results;\n break;\n }\n }\n\n if (matchedFixture) {\n logger.debug(`Rerank fixture matched for query \"${query.slice(0, 80)}\"`);\n } else {\n logger.debug(`No rerank fixture matched for query \"${query.slice(0, 80)}\" — returning empty`);\n }\n\n // Build response (Cohere rerank v2 format: index + relevance_score only)\n const results = matchedResults.map((r) => ({\n index: r.index,\n relevance_score: r.relevance_score,\n }));\n\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/rerank\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"rerank\",\n response: { status: 200, fixture: null },\n });\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n id: generateId(\"rerank\"),\n results,\n meta: {\n billed_units: { search_units: 0 },\n },\n }),\n );\n}\n"],"mappings":";;;AA2BA,eAAsB,aACpB,KACA,KACA,KACA,UACA,SACA,UACA,gBACe;CACf,MAAM,EAAE,WAAW;AACnB,gBAAe,IAAI;CAEnB,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,IAAI;UACf,UAAU;EACjB,MAAM,SAAS,oBAAoB,QAAQ,SAAS,UAAU;AAC9D,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,SAAS;GACT,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IACF,KAAK,UAAU,EACb,OAAO;GACL,SAAS,mBAAmB;GAC5B,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAGF,MAAM,QAAQ,KAAK,SAAS;CAG5B,IAAI,iBAAiC,EAAE;CACvC,IAAI,iBAAuC;AAE3C,MAAK,MAAM,WAAW,SACpB,KAAI,eAAe,OAAO,QAAQ,MAAM,EAAE;AACxC,mBAAiB;AACjB,mBAAiB,QAAQ;AACzB;;AAIJ,KAAI,eACF,QAAO,MAAM,qCAAqC,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG;KAExE,QAAO,MAAM,wCAAwC,MAAM,MAAM,GAAG,GAAG,CAAC,qBAAqB;CAI/F,MAAM,UAAU,eAAe,KAAK,OAAO;EACzC,OAAO,EAAE;EACT,iBAAiB,EAAE;EACpB,EAAE;AAEH,SAAQ,IAAI;EACV,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAAS,eAAe,IAAI,QAAQ;EACpC,MAAM;EACN,SAAS;EACT,UAAU;GAAE,QAAQ;GAAK,SAAS;GAAM;EACzC,CAAC;AAEF,KAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,KAAI,IACF,KAAK,UAAU;EACb,IAAI,WAAW,SAAS;EACxB;EACA,MAAM,EACJ,cAAc,EAAE,cAAc,GAAG,EAClC;EACF,CAAC,CACH"}
1
+ {"version":3,"file":"rerank.js","names":[],"sources":["../src/rerank.ts"],"sourcesContent":["/**\n * Reranking API support for LLMock.\n *\n * Handles POST /v2/rerank requests (Cohere rerank-compatible). Matches\n * fixtures by comparing the request `query` field against registered\n * patterns. First match wins; no match returns empty results.\n */\n\nimport type * as http from \"node:http\";\nimport {\n flattenHeaders,\n generateId,\n isJsonObject,\n matchesPattern,\n normalizeTextInput,\n resolveStrictMode,\n strictNoMatchLogLine,\n strictNoMatchMessage,\n strictOverrideField,\n} from \"./helpers.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { Logger } from \"./logger.js\";\n\n// ─── Rerank types ─────────────────────────────────────────────────────────\n\nexport interface RerankResult {\n index: number;\n relevance_score: number;\n}\n\nexport interface RerankFixture {\n match: string | RegExp;\n results: RerankResult[];\n}\n\n// ─── Request handler ──────────────────────────────────────────────────────\n\nexport async function handleRerank(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n raw: string,\n fixtures: RerankFixture[],\n journal: Journal,\n defaults: { logger: Logger; strict?: boolean },\n setCorsHeaders: (res: http.ServerResponse) => void,\n): Promise<void> {\n const { logger } = defaults;\n setCorsHeaders(res);\n\n let body: { query?: unknown; model?: string };\n try {\n body = JSON.parse(raw) as { query?: unknown; model?: string };\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/rerank\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"rerank\",\n response: { status: 400, fixture: null },\n });\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: `Malformed JSON: ${detail}`,\n type: \"invalid_request_error\",\n code: \"invalid_json\",\n },\n }),\n );\n return;\n }\n\n // Reject bodies that parsed but are not a JSON object (e.g. `null`) before\n // touching fields — otherwise `body.query` throws a TypeError that surfaces\n // as a 500 instead of a 400.\n if (!isJsonObject(body)) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/rerank\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"rerank\",\n response: { status: 400, fixture: null },\n });\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: \"Request body must be a JSON object\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n\n const rawQuery: unknown = body.query ?? \"\";\n const normalizedQuery = normalizeTextInput(rawQuery);\n if (normalizedQuery === null) {\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/rerank\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"rerank\",\n response: { status: 400, fixture: null },\n });\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: \"Invalid parameter: 'query' must be a string or an array of strings\",\n type: \"invalid_request_error\",\n },\n }),\n );\n return;\n }\n const query = normalizedQuery;\n\n // Find first matching fixture\n let matchedResults: RerankResult[] = [];\n let matchedFixture: RerankFixture | null = null;\n\n for (const fixture of fixtures) {\n if (matchesPattern(query, fixture.match)) {\n matchedFixture = fixture;\n matchedResults = fixture.results;\n break;\n }\n }\n\n if (matchedFixture) {\n logger.debug(`Rerank fixture matched for query \"${query.slice(0, 80)}\"`);\n } else {\n logger.debug(`No rerank fixture matched for query \"${query.slice(0, 80)}\" — returning empty`);\n }\n\n // Strict mode turns the lenient default (empty results) into a no-match\n // error, like every other fixture-matching handler. These fixtures carry no\n // sequence/turn state, so the skipped count is always 0.\n if (!matchedFixture && resolveStrictMode(defaults.strict, req.headers)) {\n const strictMessage = strictNoMatchMessage(0);\n logger.error(strictNoMatchLogLine(req.method ?? \"POST\", req.url ?? \"/v2/rerank\", 0));\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/rerank\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"rerank\",\n response: {\n status: 503,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n res.writeHead(503, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: {\n message: strictMessage,\n type: \"invalid_request_error\",\n code: \"no_fixture_match\",\n },\n }),\n );\n return;\n }\n\n // Build response (Cohere rerank v2 format: index + relevance_score only)\n const results = matchedResults.map((r) => ({\n index: r.index,\n relevance_score: r.relevance_score,\n }));\n\n journal.add({\n method: req.method ?? \"POST\",\n path: req.url ?? \"/v2/rerank\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"rerank\",\n response: {\n status: 200,\n fixture: null,\n ...strictOverrideField(defaults.strict, req.headers),\n },\n });\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n id: generateId(\"rerank\"),\n results,\n meta: {\n billed_units: { search_units: 0 },\n },\n }),\n );\n}\n"],"mappings":";;;AAqCA,eAAsB,aACpB,KACA,KACA,KACA,UACA,SACA,UACA,gBACe;CACf,MAAM,EAAE,WAAW;AACnB,gBAAe,IAAI;CAEnB,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,IAAI;UACf,UAAU;EACjB,MAAM,SAAS,oBAAoB,QAAQ,SAAS,UAAU;AAC9D,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,SAAS;GACT,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IACF,KAAK,UAAU,EACb,OAAO;GACL,SAAS,mBAAmB;GAC5B,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;AAMF,KAAI,CAAC,aAAa,KAAK,EAAE;AACvB,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,SAAS;GACT,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IACF,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAIF,MAAM,kBAAkB,mBADE,KAAK,SAAS,GACY;AACpD,KAAI,oBAAoB,MAAM;AAC5B,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,SAAS;GACT,UAAU;IAAE,QAAQ;IAAK,SAAS;IAAM;GACzC,CAAC;AACF,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IACF,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAEF,MAAM,QAAQ;CAGd,IAAI,iBAAiC,EAAE;CACvC,IAAI,iBAAuC;AAE3C,MAAK,MAAM,WAAW,SACpB,KAAI,eAAe,OAAO,QAAQ,MAAM,EAAE;AACxC,mBAAiB;AACjB,mBAAiB,QAAQ;AACzB;;AAIJ,KAAI,eACF,QAAO,MAAM,qCAAqC,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG;KAExE,QAAO,MAAM,wCAAwC,MAAM,MAAM,GAAG,GAAG,CAAC,qBAAqB;AAM/F,KAAI,CAAC,kBAAkB,kBAAkB,SAAS,QAAQ,IAAI,QAAQ,EAAE;EACtE,MAAM,gBAAgB,qBAAqB,EAAE;AAC7C,SAAO,MAAM,qBAAqB,IAAI,UAAU,QAAQ,IAAI,OAAO,cAAc,EAAE,CAAC;AACpF,UAAQ,IAAI;GACV,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAAS,eAAe,IAAI,QAAQ;GACpC,MAAM;GACN,SAAS;GACT,UAAU;IACR,QAAQ;IACR,SAAS;IACT,GAAG,oBAAoB,SAAS,QAAQ,IAAI,QAAQ;IACrD;GACF,CAAC;AACF,MAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,MAAI,IACF,KAAK,UAAU,EACb,OAAO;GACL,SAAS;GACT,MAAM;GACN,MAAM;GACP,EACF,CAAC,CACH;AACD;;CAIF,MAAM,UAAU,eAAe,KAAK,OAAO;EACzC,OAAO,EAAE;EACT,iBAAiB,EAAE;EACpB,EAAE;AAEH,SAAQ,IAAI;EACV,QAAQ,IAAI,UAAU;EACtB,MAAM,IAAI,OAAO;EACjB,SAAS,eAAe,IAAI,QAAQ;EACpC,MAAM;EACN,SAAS;EACT,UAAU;GACR,QAAQ;GACR,SAAS;GACT,GAAG,oBAAoB,SAAS,QAAQ,IAAI,QAAQ;GACrD;EACF,CAAC;AAEF,KAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,KAAI,IACF,KAAK,UAAU;EACb,IAAI,WAAW,SAAS;EACxB;EACA,MAAM,EACJ,cAAc,EAAE,cAAc,GAAG,EAClC;EACF,CAAC,CACH"}
@@ -852,6 +852,23 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
852
852
  } }));
853
853
  return;
854
854
  }
855
+ if (!require_helpers.isJsonObject(responsesReq)) {
856
+ journal.add({
857
+ method: req.method ?? "POST",
858
+ path: req.url ?? "/v1/responses",
859
+ headers: require_helpers.flattenHeaders(req.headers),
860
+ body: null,
861
+ response: {
862
+ status: 400,
863
+ fixture: null
864
+ }
865
+ });
866
+ require_sse_writer.writeErrorResponse(res, 400, JSON.stringify({ error: {
867
+ message: "Request body must be a JSON object",
868
+ type: "invalid_request_error"
869
+ } }));
870
+ return;
871
+ }
855
872
  const completionReq = responsesToCompletionRequest(responsesReq);
856
873
  completionReq._endpointType = "chat";
857
874
  completionReq._context = require_helpers.getContext(req);
@@ -863,7 +880,7 @@ async function handleResponses(req, res, raw, fixtures, journal, defaults, setCo
863
880
  defaults.logger.debug(`Responses fixture matched for ${req.method ?? "POST"} ${req.url ?? "/v1/responses"}`);
864
881
  journal.incrementFixtureMatchCount(fixture, fixtures, testId);
865
882
  } else defaults.logger.debug(`No responses fixture matched for ${req.method ?? "POST"} ${req.url ?? "/v1/responses"}`);
866
- if (require_chaos.applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
883
+ if (require_chaos.applyChaos(res, fixture, defaults.chaos, req.headers, req.url, journal, {
867
884
  method: req.method ?? "POST",
868
885
  path: req.url ?? "/v1/responses",
869
886
  headers: require_helpers.flattenHeaders(req.headers),