@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
@@ -60,10 +60,40 @@ var VectorMock = class {
60
60
  if (body) parsed = JSON.parse(body);
61
61
  } catch (parseErr) {
62
62
  const detail = parseErr instanceof Error ? parseErr.message : "unknown";
63
+ if (this.journal) this.journal.add({
64
+ method: req.method ?? "GET",
65
+ path: req.url ?? "/",
66
+ headers: require_helpers.flattenHeaders(req.headers),
67
+ body: null,
68
+ service: "vector",
69
+ response: {
70
+ status: 400,
71
+ fixture: null
72
+ }
73
+ });
63
74
  res.writeHead(400, { "Content-Type": "application/json" });
64
75
  res.end(JSON.stringify({ error: `Malformed JSON body: ${detail}` }));
65
76
  return true;
66
77
  }
78
+ if (!require_helpers.isJsonObject(parsed)) {
79
+ if (this.journal) this.journal.add({
80
+ method: req.method ?? "GET",
81
+ path: req.url ?? "/",
82
+ headers: require_helpers.flattenHeaders(req.headers),
83
+ body: null,
84
+ service: "vector",
85
+ response: {
86
+ status: 400,
87
+ fixture: null
88
+ }
89
+ });
90
+ res.writeHead(400, { "Content-Type": "application/json" });
91
+ res.end(JSON.stringify({ error: {
92
+ message: "Request body must be a JSON object",
93
+ type: "invalid_request_error"
94
+ } }));
95
+ return true;
96
+ }
67
97
  }
68
98
  const handled = this.requestHandler(req, res, pathname, parsed);
69
99
  if (handled && this.registry) {
@@ -84,6 +114,23 @@ var VectorMock = class {
84
114
  fixture: null
85
115
  }
86
116
  });
117
+ if (!handled && this.journal) {
118
+ const journal = this.journal;
119
+ res.once("finish", () => {
120
+ if (res.statusCode !== 404) return;
121
+ journal.add({
122
+ method: req.method ?? "GET",
123
+ path: req.url ?? "/",
124
+ headers: require_helpers.flattenHeaders(req.headers),
125
+ body: null,
126
+ service: "vector",
127
+ response: {
128
+ status: 404,
129
+ fixture: null
130
+ }
131
+ });
132
+ });
133
+ }
87
134
  return handled;
88
135
  }
89
136
  health() {
@@ -114,11 +161,41 @@ var VectorMock = class {
114
161
  } catch (parseErr) {
115
162
  if (req.method !== "GET") {
116
163
  const detail = parseErr instanceof Error ? parseErr.message : "unknown";
164
+ if (this.journal) this.journal.add({
165
+ method: req.method ?? "GET",
166
+ path: req.url ?? "/",
167
+ headers: require_helpers.flattenHeaders(req.headers),
168
+ body: null,
169
+ service: "vector",
170
+ response: {
171
+ status: 400,
172
+ fixture: null
173
+ }
174
+ });
117
175
  res.writeHead(400, { "Content-Type": "application/json" });
118
176
  res.end(JSON.stringify({ error: `Malformed JSON body: ${detail}` }));
119
177
  return;
120
178
  }
121
179
  }
180
+ if (req.method !== "GET" && !require_helpers.isJsonObject(parsed)) {
181
+ if (this.journal) this.journal.add({
182
+ method: req.method ?? "GET",
183
+ path: req.url ?? "/",
184
+ headers: require_helpers.flattenHeaders(req.headers),
185
+ body: null,
186
+ service: "vector",
187
+ response: {
188
+ status: 400,
189
+ fixture: null
190
+ }
191
+ });
192
+ res.writeHead(400, { "Content-Type": "application/json" });
193
+ res.end(JSON.stringify({ error: {
194
+ message: "Request body must be a JSON object",
195
+ type: "invalid_request_error"
196
+ } }));
197
+ return;
198
+ }
122
199
  const url = new URL(req.url ?? "/", `http://${host}`);
123
200
  const handled = this.requestHandler(req, res, url.pathname, parsed);
124
201
  if (handled && this.journal) this.journal.add({
@@ -133,6 +210,17 @@ var VectorMock = class {
133
210
  }
134
211
  });
135
212
  if (!handled) {
213
+ if (this.journal) this.journal.add({
214
+ method: req.method ?? "GET",
215
+ path: req.url ?? "/",
216
+ headers: require_helpers.flattenHeaders(req.headers),
217
+ body: null,
218
+ service: "vector",
219
+ response: {
220
+ status: 404,
221
+ fixture: null
222
+ }
223
+ });
136
224
  res.writeHead(404, { "Content-Type": "application/json" });
137
225
  res.end(JSON.stringify({ error: "Not found" }));
138
226
  }
@@ -1 +1 @@
1
- {"version":3,"file":"vector-mock.cjs","names":["readBody","flattenHeaders","http","createVectorRequestHandler"],"sources":["../src/vector-mock.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport type { Mountable, JournalEntry } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type {\n VectorMockOptions,\n VectorCollection,\n VectorEntry,\n QueryResult,\n VectorQuery,\n QueryHandler,\n} from \"./vector-types.js\";\nimport { createVectorRequestHandler, type VectorState } from \"./vector-handler.js\";\nimport { flattenHeaders, readBody } from \"./helpers.js\";\n\nexport class VectorMock implements Mountable {\n private collections: Map<string, VectorCollection> = new Map();\n private queryHandlers: Map<string, QueryHandler> = new Map();\n private server: http.Server | null = null;\n private journal: Journal | null = null;\n private registry: MetricsRegistry | null = null;\n private options: VectorMockOptions;\n private requestHandler: ReturnType<typeof createVectorRequestHandler>;\n\n constructor(options?: VectorMockOptions) {\n this.options = options ?? {};\n this.requestHandler = this.buildHandler();\n }\n\n // ---- Configuration ----\n\n addCollection(name: string, opts: { dimension: number }): this {\n const collection: VectorCollection = {\n name,\n dimension: opts.dimension,\n vectors: new Map(),\n };\n this.collections.set(name, collection);\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n upsert(collection: string, vectors: VectorEntry[]): this {\n let col = this.collections.get(collection);\n if (!col) {\n const dim = vectors.length > 0 ? vectors[0].values.length : 0;\n col = { name: collection, dimension: dim, vectors: new Map() };\n this.collections.set(collection, col);\n }\n for (const v of vectors) {\n col.vectors.set(v.id, v);\n }\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n onQuery(\n collection: string,\n results: QueryResult[] | ((query: VectorQuery) => QueryResult[]),\n ): this {\n this.queryHandlers.set(collection, results);\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n deleteCollection(name: string): this {\n this.collections.delete(name);\n this.queryHandlers.delete(name);\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n // ---- Mountable interface ----\n\n async handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n ): Promise<boolean> {\n // Only consume the request body for methods that carry a body,\n // avoiding double-consumption when the route doesn't match.\n let parsed: Record<string, unknown> = {};\n if (req.method !== \"GET\" && req.method !== \"DELETE\" && req.method !== \"HEAD\") {\n const body = await readBody(req);\n try {\n if (body) parsed = JSON.parse(body);\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Malformed JSON body: ${detail}` }));\n return true;\n }\n }\n\n const handled = this.requestHandler(req, res, pathname, parsed);\n\n // Record vector operation metric\n if (handled && this.registry) {\n const { operation, provider } = classifyVectorRequest(req.method ?? \"GET\", pathname);\n this.registry.incrementCounter(\"aimock_vector_requests_total\", { operation, provider });\n }\n\n // Journal the request after the handler completes\n if (handled && this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n\n return handled;\n }\n\n health(): { status: string; collections: number } {\n return {\n status: \"ok\",\n collections: this.collections.size,\n };\n }\n\n setJournal(journal: Journal): void {\n this.journal = journal;\n }\n\n setRegistry(registry: MetricsRegistry): void {\n this.registry = registry;\n }\n\n // ---- Standalone mode ----\n\n async start(): Promise<string> {\n if (this.server) {\n throw new Error(\"Server already started\");\n }\n\n const host = this.options.host ?? \"127.0.0.1\";\n const port = this.options.port ?? 0;\n\n return new Promise((resolve, reject) => {\n const srv = http.createServer((req, res) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => {\n const body = Buffer.concat(chunks).toString();\n let parsed: Record<string, unknown> = {};\n try {\n if (body) parsed = JSON.parse(body);\n } catch (parseErr) {\n if (req.method !== \"GET\") {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Malformed JSON body: ${detail}` }));\n return;\n }\n }\n\n const url = new URL(req.url ?? \"/\", `http://${host}`);\n\n const handled = this.requestHandler(req, res, url.pathname, parsed);\n\n if (handled && this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n if (!handled) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not found\" }));\n }\n });\n });\n\n srv.listen(port, host, () => {\n this.server = srv;\n const addr = srv.address();\n if (typeof addr === \"object\" && addr !== null) {\n resolve(`http://${host}:${addr.port}`);\n } else {\n resolve(`http://${host}:${port}`);\n }\n });\n\n srv.on(\"error\", reject);\n });\n }\n\n async stop(): Promise<void> {\n if (!this.server) {\n throw new Error(\"Server not started\");\n }\n const srv = this.server;\n this.server = null;\n await new Promise<void>((resolve, reject) => {\n srv.close((err) => (err ? reject(err) : resolve()));\n });\n }\n\n // ---- Inspection ----\n\n getRequests(): JournalEntry[] {\n if (!this.journal) return [];\n return this.journal.getAll().filter((e) => e.service === \"vector\");\n }\n\n reset(): this {\n this.collections.clear();\n this.queryHandlers.clear();\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n // ---- Internal ----\n\n private buildHandler() {\n const state: VectorState = {\n collections: this.collections,\n queryHandlers: this.queryHandlers,\n };\n return createVectorRequestHandler(state);\n }\n}\n\n// ---- Helpers ----\n\n/**\n * Classify a vector request by operation and provider based on HTTP method and pathname.\n */\nfunction classifyVectorRequest(\n method: string,\n pathname: string,\n): { operation: string; provider: string } {\n // Pinecone paths\n if (pathname === \"/query\" && method === \"POST\") {\n return { operation: \"query\", provider: \"pinecone\" };\n }\n if (pathname === \"/vectors/upsert\" && method === \"POST\") {\n return { operation: \"upsert\", provider: \"pinecone\" };\n }\n if (pathname === \"/vectors/delete\" && method === \"POST\") {\n return { operation: \"delete\", provider: \"pinecone\" };\n }\n if (pathname === \"/describe-index-stats\" && method === \"GET\") {\n return { operation: \"describe\", provider: \"pinecone\" };\n }\n\n // Qdrant paths\n if (/^\\/collections\\/[^/]+\\/points\\/search$/.test(pathname) && method === \"POST\") {\n return { operation: \"query\", provider: \"qdrant\" };\n }\n if (/^\\/collections\\/[^/]+\\/points$/.test(pathname) && method === \"PUT\") {\n return { operation: \"upsert\", provider: \"qdrant\" };\n }\n if (/^\\/collections\\/[^/]+\\/points\\/delete$/.test(pathname) && method === \"POST\") {\n return { operation: \"delete\", provider: \"qdrant\" };\n }\n\n // ChromaDB paths\n if (/^\\/api\\/v1\\/collections\\/[^/]+\\/query$/.test(pathname) && method === \"POST\") {\n return { operation: \"query\", provider: \"chromadb\" };\n }\n if (/^\\/api\\/v1\\/collections\\/[^/]+\\/add$/.test(pathname) && method === \"POST\") {\n return { operation: \"upsert\", provider: \"chromadb\" };\n }\n if (pathname === \"/api/v1/collections\" && method === \"GET\") {\n return { operation: \"list\", provider: \"chromadb\" };\n }\n if (/^\\/api\\/v1\\/collections\\/[^/]+$/.test(pathname) && method === \"DELETE\") {\n return { operation: \"delete\", provider: \"chromadb\" };\n }\n\n return { operation: \"unknown\", provider: \"unknown\" };\n}\n"],"mappings":";;;;;;;AAeA,IAAa,aAAb,MAA6C;CAC3C,AAAQ,8BAA6C,IAAI,KAAK;CAC9D,AAAQ,gCAA2C,IAAI,KAAK;CAC5D,AAAQ,SAA6B;CACrC,AAAQ,UAA0B;CAClC,AAAQ,WAAmC;CAC3C,AAAQ;CACR,AAAQ;CAER,YAAY,SAA6B;AACvC,OAAK,UAAU,WAAW,EAAE;AAC5B,OAAK,iBAAiB,KAAK,cAAc;;CAK3C,cAAc,MAAc,MAAmC;EAC7D,MAAM,aAA+B;GACnC;GACA,WAAW,KAAK;GAChB,yBAAS,IAAI,KAAK;GACnB;AACD,OAAK,YAAY,IAAI,MAAM,WAAW;AACtC,OAAK,iBAAiB,KAAK,cAAc;AACzC,SAAO;;CAGT,OAAO,YAAoB,SAA8B;EACvD,IAAI,MAAM,KAAK,YAAY,IAAI,WAAW;AAC1C,MAAI,CAAC,KAAK;AAER,SAAM;IAAE,MAAM;IAAY,WADd,QAAQ,SAAS,IAAI,QAAQ,GAAG,OAAO,SAAS;IAClB,yBAAS,IAAI,KAAK;IAAE;AAC9D,QAAK,YAAY,IAAI,YAAY,IAAI;;AAEvC,OAAK,MAAM,KAAK,QACd,KAAI,QAAQ,IAAI,EAAE,IAAI,EAAE;AAE1B,OAAK,iBAAiB,KAAK,cAAc;AACzC,SAAO;;CAGT,QACE,YACA,SACM;AACN,OAAK,cAAc,IAAI,YAAY,QAAQ;AAC3C,OAAK,iBAAiB,KAAK,cAAc;AACzC,SAAO;;CAGT,iBAAiB,MAAoB;AACnC,OAAK,YAAY,OAAO,KAAK;AAC7B,OAAK,cAAc,OAAO,KAAK;AAC/B,OAAK,iBAAiB,KAAK,cAAc;AACzC,SAAO;;CAKT,MAAM,cACJ,KACA,KACA,UACkB;EAGlB,IAAI,SAAkC,EAAE;AACxC,MAAI,IAAI,WAAW,SAAS,IAAI,WAAW,YAAY,IAAI,WAAW,QAAQ;GAC5E,MAAM,OAAO,MAAMA,yBAAS,IAAI;AAChC,OAAI;AACF,QAAI,KAAM,UAAS,KAAK,MAAM,KAAK;YAC5B,UAAU;IACjB,MAAM,SAAS,oBAAoB,QAAQ,SAAS,UAAU;AAC9D,QAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,QAAI,IAAI,KAAK,UAAU,EAAE,OAAO,wBAAwB,UAAU,CAAC,CAAC;AACpE,WAAO;;;EAIX,MAAM,UAAU,KAAK,eAAe,KAAK,KAAK,UAAU,OAAO;AAG/D,MAAI,WAAW,KAAK,UAAU;GAC5B,MAAM,EAAE,WAAW,aAAa,sBAAsB,IAAI,UAAU,OAAO,SAAS;AACpF,QAAK,SAAS,iBAAiB,gCAAgC;IAAE;IAAW;IAAU,CAAC;;AAIzF,MAAI,WAAW,KAAK,QAClB,MAAK,QAAQ,IAAI;GACf,QAAQ,IAAI,UAAU;GACtB,MAAM,IAAI,OAAO;GACjB,SAASC,+BAAe,IAAI,QAAQ;GACpC,MAAM;GACN,SAAS;GACT,UAAU;IAAE,QAAQ,IAAI;IAAY,SAAS;IAAM;GACpD,CAAC;AAGJ,SAAO;;CAGT,SAAkD;AAChD,SAAO;GACL,QAAQ;GACR,aAAa,KAAK,YAAY;GAC/B;;CAGH,WAAW,SAAwB;AACjC,OAAK,UAAU;;CAGjB,YAAY,UAAiC;AAC3C,OAAK,WAAW;;CAKlB,MAAM,QAAyB;AAC7B,MAAI,KAAK,OACP,OAAM,IAAI,MAAM,yBAAyB;EAG3C,MAAM,OAAO,KAAK,QAAQ,QAAQ;EAClC,MAAM,OAAO,KAAK,QAAQ,QAAQ;AAElC,SAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,MAAMC,UAAK,cAAc,KAAK,QAAQ;IAC1C,MAAM,SAAmB,EAAE;AAC3B,QAAI,GAAG,SAAS,UAAkB,OAAO,KAAK,MAAM,CAAC;AACrD,QAAI,GAAG,aAAa;KAClB,MAAM,OAAO,OAAO,OAAO,OAAO,CAAC,UAAU;KAC7C,IAAI,SAAkC,EAAE;AACxC,SAAI;AACF,UAAI,KAAM,UAAS,KAAK,MAAM,KAAK;cAC5B,UAAU;AACjB,UAAI,IAAI,WAAW,OAAO;OACxB,MAAM,SAAS,oBAAoB,QAAQ,SAAS,UAAU;AAC9D,WAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,WAAI,IAAI,KAAK,UAAU,EAAE,OAAO,wBAAwB,UAAU,CAAC,CAAC;AACpE;;;KAIJ,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,OAAO;KAErD,MAAM,UAAU,KAAK,eAAe,KAAK,KAAK,IAAI,UAAU,OAAO;AAEnE,SAAI,WAAW,KAAK,QAClB,MAAK,QAAQ,IAAI;MACf,QAAQ,IAAI,UAAU;MACtB,MAAM,IAAI,OAAO;MACjB,SAASD,+BAAe,IAAI,QAAQ;MACpC,MAAM;MACN,SAAS;MACT,UAAU;OAAE,QAAQ,IAAI;OAAY,SAAS;OAAM;MACpD,CAAC;AAEJ,SAAI,CAAC,SAAS;AACZ,UAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,aAAa,CAAC,CAAC;;MAEjD;KACF;AAEF,OAAI,OAAO,MAAM,YAAY;AAC3B,SAAK,SAAS;IACd,MAAM,OAAO,IAAI,SAAS;AAC1B,QAAI,OAAO,SAAS,YAAY,SAAS,KACvC,SAAQ,UAAU,KAAK,GAAG,KAAK,OAAO;QAEtC,SAAQ,UAAU,KAAK,GAAG,OAAO;KAEnC;AAEF,OAAI,GAAG,SAAS,OAAO;IACvB;;CAGJ,MAAM,OAAsB;AAC1B,MAAI,CAAC,KAAK,OACR,OAAM,IAAI,MAAM,qBAAqB;EAEvC,MAAM,MAAM,KAAK;AACjB,OAAK,SAAS;AACd,QAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,OAAI,OAAO,QAAS,MAAM,OAAO,IAAI,GAAG,SAAS,CAAE;IACnD;;CAKJ,cAA8B;AAC5B,MAAI,CAAC,KAAK,QAAS,QAAO,EAAE;AAC5B,SAAO,KAAK,QAAQ,QAAQ,CAAC,QAAQ,MAAM,EAAE,YAAY,SAAS;;CAGpE,QAAc;AACZ,OAAK,YAAY,OAAO;AACxB,OAAK,cAAc,OAAO;AAC1B,OAAK,iBAAiB,KAAK,cAAc;AACzC,SAAO;;CAKT,AAAQ,eAAe;AAKrB,SAAOE,kDAJoB;GACzB,aAAa,KAAK;GAClB,eAAe,KAAK;GACrB,CACuC;;;;;;AAS5C,SAAS,sBACP,QACA,UACyC;AAEzC,KAAI,aAAa,YAAY,WAAW,OACtC,QAAO;EAAE,WAAW;EAAS,UAAU;EAAY;AAErD,KAAI,aAAa,qBAAqB,WAAW,OAC/C,QAAO;EAAE,WAAW;EAAU,UAAU;EAAY;AAEtD,KAAI,aAAa,qBAAqB,WAAW,OAC/C,QAAO;EAAE,WAAW;EAAU,UAAU;EAAY;AAEtD,KAAI,aAAa,2BAA2B,WAAW,MACrD,QAAO;EAAE,WAAW;EAAY,UAAU;EAAY;AAIxD,KAAI,yCAAyC,KAAK,SAAS,IAAI,WAAW,OACxE,QAAO;EAAE,WAAW;EAAS,UAAU;EAAU;AAEnD,KAAI,iCAAiC,KAAK,SAAS,IAAI,WAAW,MAChE,QAAO;EAAE,WAAW;EAAU,UAAU;EAAU;AAEpD,KAAI,yCAAyC,KAAK,SAAS,IAAI,WAAW,OACxE,QAAO;EAAE,WAAW;EAAU,UAAU;EAAU;AAIpD,KAAI,yCAAyC,KAAK,SAAS,IAAI,WAAW,OACxE,QAAO;EAAE,WAAW;EAAS,UAAU;EAAY;AAErD,KAAI,uCAAuC,KAAK,SAAS,IAAI,WAAW,OACtE,QAAO;EAAE,WAAW;EAAU,UAAU;EAAY;AAEtD,KAAI,aAAa,yBAAyB,WAAW,MACnD,QAAO;EAAE,WAAW;EAAQ,UAAU;EAAY;AAEpD,KAAI,kCAAkC,KAAK,SAAS,IAAI,WAAW,SACjE,QAAO;EAAE,WAAW;EAAU,UAAU;EAAY;AAGtD,QAAO;EAAE,WAAW;EAAW,UAAU;EAAW"}
1
+ {"version":3,"file":"vector-mock.cjs","names":["readBody","flattenHeaders","isJsonObject","http","createVectorRequestHandler"],"sources":["../src/vector-mock.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport type { Mountable, JournalEntry } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type {\n VectorMockOptions,\n VectorCollection,\n VectorEntry,\n QueryResult,\n VectorQuery,\n QueryHandler,\n} from \"./vector-types.js\";\nimport { createVectorRequestHandler, type VectorState } from \"./vector-handler.js\";\nimport { flattenHeaders, isJsonObject, readBody } from \"./helpers.js\";\n\nexport class VectorMock implements Mountable {\n private collections: Map<string, VectorCollection> = new Map();\n private queryHandlers: Map<string, QueryHandler> = new Map();\n private server: http.Server | null = null;\n private journal: Journal | null = null;\n private registry: MetricsRegistry | null = null;\n private options: VectorMockOptions;\n private requestHandler: ReturnType<typeof createVectorRequestHandler>;\n\n constructor(options?: VectorMockOptions) {\n this.options = options ?? {};\n this.requestHandler = this.buildHandler();\n }\n\n // ---- Configuration ----\n\n addCollection(name: string, opts: { dimension: number }): this {\n const collection: VectorCollection = {\n name,\n dimension: opts.dimension,\n vectors: new Map(),\n };\n this.collections.set(name, collection);\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n upsert(collection: string, vectors: VectorEntry[]): this {\n let col = this.collections.get(collection);\n if (!col) {\n const dim = vectors.length > 0 ? vectors[0].values.length : 0;\n col = { name: collection, dimension: dim, vectors: new Map() };\n this.collections.set(collection, col);\n }\n for (const v of vectors) {\n col.vectors.set(v.id, v);\n }\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n onQuery(\n collection: string,\n results: QueryResult[] | ((query: VectorQuery) => QueryResult[]),\n ): this {\n this.queryHandlers.set(collection, results);\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n deleteCollection(name: string): this {\n this.collections.delete(name);\n this.queryHandlers.delete(name);\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n // ---- Mountable interface ----\n\n async handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n ): Promise<boolean> {\n // Only consume the request body for methods that carry a body,\n // avoiding double-consumption when the route doesn't match.\n let parsed: Record<string, unknown> = {};\n if (req.method !== \"GET\" && req.method !== \"DELETE\" && req.method !== \"HEAD\") {\n const body = await readBody(req);\n try {\n if (body) parsed = JSON.parse(body);\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: 400, fixture: null },\n });\n }\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Malformed JSON body: ${detail}` }));\n return true;\n }\n\n // Reject bodies that parsed but are not a JSON object (e.g. `null`)\n // before the route handlers touch fields — `body.ids`, `body.vectors`,\n // `body.points` and friends throw a TypeError on `null` that nothing\n // catches, killing the process rather than answering the request.\n // Arrays and scalars are rejected for the same reason the JSON handlers\n // reject them: every field reads as `undefined` and the handler answers\n // a misleading `200` on a degenerate empty result.\n if (!isJsonObject(parsed)) {\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: 400, fixture: null },\n });\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 true;\n }\n }\n\n const handled = this.requestHandler(req, res, pathname, parsed);\n\n // Record vector operation metric\n if (handled && this.registry) {\n const { operation, provider } = classifyVectorRequest(req.method ?? \"GET\", pathname);\n this.registry.incrementCounter(\"aimock_vector_requests_total\", { operation, provider });\n }\n\n // Journal the request after the handler completes\n if (handled && this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n\n // When mounted, an unmatched path is answered by the parent server, not\n // here, so journal it once the response is out and only if it really was\n // a 404 — otherwise a path this wrapper declines but the server rewrites\n // and serves would be journaled twice. Keeps mounted mode in step with\n // the standalone 404 journaling below.\n if (!handled && this.journal) {\n const journal = this.journal;\n res.once(\"finish\", () => {\n if (res.statusCode !== 404) return;\n journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: 404, fixture: null },\n });\n });\n }\n\n return handled;\n }\n\n health(): { status: string; collections: number } {\n return {\n status: \"ok\",\n collections: this.collections.size,\n };\n }\n\n setJournal(journal: Journal): void {\n this.journal = journal;\n }\n\n setRegistry(registry: MetricsRegistry): void {\n this.registry = registry;\n }\n\n // ---- Standalone mode ----\n\n async start(): Promise<string> {\n if (this.server) {\n throw new Error(\"Server already started\");\n }\n\n const host = this.options.host ?? \"127.0.0.1\";\n const port = this.options.port ?? 0;\n\n return new Promise((resolve, reject) => {\n const srv = http.createServer((req, res) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => {\n const body = Buffer.concat(chunks).toString();\n let parsed: Record<string, unknown> = {};\n try {\n if (body) parsed = JSON.parse(body);\n } catch (parseErr) {\n if (req.method !== \"GET\") {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: 400, fixture: null },\n });\n }\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Malformed JSON body: ${detail}` }));\n return;\n }\n }\n\n // Same non-object rejection as the mounted path above — a `null`\n // body otherwise reaches the route handlers and throws an uncaught\n // TypeError that takes the standalone server's process down.\n if (req.method !== \"GET\" && !isJsonObject(parsed)) {\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: 400, fixture: null },\n });\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 url = new URL(req.url ?? \"/\", `http://${host}`);\n\n const handled = this.requestHandler(req, res, url.pathname, parsed);\n\n if (handled && this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n if (!handled) {\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: 404, fixture: null },\n });\n }\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not found\" }));\n }\n });\n });\n\n srv.listen(port, host, () => {\n this.server = srv;\n const addr = srv.address();\n if (typeof addr === \"object\" && addr !== null) {\n resolve(`http://${host}:${addr.port}`);\n } else {\n resolve(`http://${host}:${port}`);\n }\n });\n\n srv.on(\"error\", reject);\n });\n }\n\n async stop(): Promise<void> {\n if (!this.server) {\n throw new Error(\"Server not started\");\n }\n const srv = this.server;\n this.server = null;\n await new Promise<void>((resolve, reject) => {\n srv.close((err) => (err ? reject(err) : resolve()));\n });\n }\n\n // ---- Inspection ----\n\n getRequests(): JournalEntry[] {\n if (!this.journal) return [];\n return this.journal.getAll().filter((e) => e.service === \"vector\");\n }\n\n reset(): this {\n this.collections.clear();\n this.queryHandlers.clear();\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n // ---- Internal ----\n\n private buildHandler() {\n const state: VectorState = {\n collections: this.collections,\n queryHandlers: this.queryHandlers,\n };\n return createVectorRequestHandler(state);\n }\n}\n\n// ---- Helpers ----\n\n/**\n * Classify a vector request by operation and provider based on HTTP method and pathname.\n */\nfunction classifyVectorRequest(\n method: string,\n pathname: string,\n): { operation: string; provider: string } {\n // Pinecone paths\n if (pathname === \"/query\" && method === \"POST\") {\n return { operation: \"query\", provider: \"pinecone\" };\n }\n if (pathname === \"/vectors/upsert\" && method === \"POST\") {\n return { operation: \"upsert\", provider: \"pinecone\" };\n }\n if (pathname === \"/vectors/delete\" && method === \"POST\") {\n return { operation: \"delete\", provider: \"pinecone\" };\n }\n if (pathname === \"/describe-index-stats\" && method === \"GET\") {\n return { operation: \"describe\", provider: \"pinecone\" };\n }\n\n // Qdrant paths\n if (/^\\/collections\\/[^/]+\\/points\\/search$/.test(pathname) && method === \"POST\") {\n return { operation: \"query\", provider: \"qdrant\" };\n }\n if (/^\\/collections\\/[^/]+\\/points$/.test(pathname) && method === \"PUT\") {\n return { operation: \"upsert\", provider: \"qdrant\" };\n }\n if (/^\\/collections\\/[^/]+\\/points\\/delete$/.test(pathname) && method === \"POST\") {\n return { operation: \"delete\", provider: \"qdrant\" };\n }\n\n // ChromaDB paths\n if (/^\\/api\\/v1\\/collections\\/[^/]+\\/query$/.test(pathname) && method === \"POST\") {\n return { operation: \"query\", provider: \"chromadb\" };\n }\n if (/^\\/api\\/v1\\/collections\\/[^/]+\\/add$/.test(pathname) && method === \"POST\") {\n return { operation: \"upsert\", provider: \"chromadb\" };\n }\n if (pathname === \"/api/v1/collections\" && method === \"GET\") {\n return { operation: \"list\", provider: \"chromadb\" };\n }\n if (/^\\/api\\/v1\\/collections\\/[^/]+$/.test(pathname) && method === \"DELETE\") {\n return { operation: \"delete\", provider: \"chromadb\" };\n }\n\n return { operation: \"unknown\", provider: \"unknown\" };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"vector-mock.d.cts","names":[],"sources":["../src/vector-mock.ts"],"sourcesContent":[],"mappings":";;;;;;;cAea,UAAA,YAAsB;;EAAtB,QAAA,aAAW;EAAA,QAAA,MAAA;UASA,OAAA;UAkBc,QAAA;UAgBzB,OAAA;UAAyB,cAAA;aAAgB,CAAA,OAAA,CAAA,EAlC9B,iBAkC8B;eAiBxC,CAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA;IACL,SAAK,EAAA,MAAA;MAET,IAAA;QA8CiB,CAAA,UAAA,EAAA,MAAA,EAAA,OAAA,EAlFgB,WAkFhB,EAAA,CAAA,EAAA,IAAA;SAIE,CAAA,UAAA,EAAA,MAAA,EAAA,OAAA,EAtEX,WAsEW,EAAA,GAAA,CAAA,CAAA,KAAA,EAtEc,WAsEd,EAAA,GAtE8B,WAsE9B,EAAA,CAAA,CAAA,EAAA,IAAA;kBAMP,CAAA,IAAA,EAAA,MAAA,CAAA,EAAA,IAAA;eA6DD,CAAA,GAAA,EAxHP,MAAA,CAAK,eAwHE,EAAA,GAAA,EAvHP,MAAA,CAAK,cAuHE,EAAA,QAAA,EAAA,MAAA,CAAA,EArHX,OAqHW,CAAA,OAAA,CAAA;QAaC,CAAA,CAAA,EAAA;IAjMkB,MAAA,EAAA,MAAA;IAAS,WAAA,EAAA,MAAA;;sBA6GtB;wBAIE;WAMP;UA6DD;iBAaC"}
1
+ {"version":3,"file":"vector-mock.d.cts","names":[],"sources":["../src/vector-mock.ts"],"sourcesContent":[],"mappings":";;;;;;;cAea,UAAA,YAAsB;;EAAtB,QAAA,aAAW;EAAA,QAAA,MAAA;UASA,OAAA;UAkBc,QAAA;UAgBzB,OAAA;UAAyB,cAAA;aAAgB,CAAA,OAAA,CAAA,EAlC9B,iBAkC8B;eAiBxC,CAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA;IACL,SAAK,EAAA,MAAA;MAET,IAAA;QA0GiB,CAAA,UAAA,EAAA,MAAA,EAAA,OAAA,EA9IgB,WA8IhB,EAAA,CAAA,EAAA,IAAA;SAIE,CAAA,UAAA,EAAA,MAAA,EAAA,OAAA,EAlIX,WAkIW,EAAA,GAAA,CAAA,CAAA,KAAA,EAlIc,WAkId,EAAA,GAlI8B,WAkI9B,EAAA,CAAA,CAAA,EAAA,IAAA;kBAMP,CAAA,IAAA,EAAA,MAAA,CAAA,EAAA,IAAA;eA2GD,CAAA,GAAA,EAlOP,MAAA,CAAK,eAkOE,EAAA,GAAA,EAjOP,MAAA,CAAK,cAiOE,EAAA,QAAA,EAAA,MAAA,CAAA,EA/NX,OA+NW,CAAA,OAAA,CAAA;QAaC,CAAA,CAAA,EAAA;IA3SkB,MAAA,EAAA,MAAA;IAAS,WAAA,EAAA,MAAA;;sBAyKtB;wBAIE;WAMP;UA2GD;iBAaC"}
@@ -1 +1 @@
1
- {"version":3,"file":"vector-mock.d.ts","names":[],"sources":["../src/vector-mock.ts"],"sourcesContent":[],"mappings":";;;;;;;cAea,UAAA,YAAsB;;EAAtB,QAAA,aAAW;EAAA,QAAA,MAAA;UASA,OAAA;UAkBc,QAAA;UAgBzB,OAAA;UAAyB,cAAA;aAAgB,CAAA,OAAA,CAAA,EAlC9B,iBAkC8B;eAiBxC,CAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA;IACL,SAAK,EAAA,MAAA;MAET,IAAA;QA8CiB,CAAA,UAAA,EAAA,MAAA,EAAA,OAAA,EAlFgB,WAkFhB,EAAA,CAAA,EAAA,IAAA;SAIE,CAAA,UAAA,EAAA,MAAA,EAAA,OAAA,EAtEX,WAsEW,EAAA,GAAA,CAAA,CAAA,KAAA,EAtEc,WAsEd,EAAA,GAtE8B,WAsE9B,EAAA,CAAA,CAAA,EAAA,IAAA;kBAMP,CAAA,IAAA,EAAA,MAAA,CAAA,EAAA,IAAA;eA6DD,CAAA,GAAA,EAxHP,MAAA,CAAK,eAwHE,EAAA,GAAA,EAvHP,MAAA,CAAK,cAuHE,EAAA,QAAA,EAAA,MAAA,CAAA,EArHX,OAqHW,CAAA,OAAA,CAAA;QAaC,CAAA,CAAA,EAAA;IAjMkB,MAAA,EAAA,MAAA;IAAS,WAAA,EAAA,MAAA;;sBA6GtB;wBAIE;WAMP;UA6DD;iBAaC"}
1
+ {"version":3,"file":"vector-mock.d.ts","names":[],"sources":["../src/vector-mock.ts"],"sourcesContent":[],"mappings":";;;;;;;cAea,UAAA,YAAsB;;EAAtB,QAAA,aAAW;EAAA,QAAA,MAAA;UASA,OAAA;UAkBc,QAAA;UAgBzB,OAAA;UAAyB,cAAA;aAAgB,CAAA,OAAA,CAAA,EAlC9B,iBAkC8B;eAiBxC,CAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA;IACL,SAAK,EAAA,MAAA;MAET,IAAA;QA0GiB,CAAA,UAAA,EAAA,MAAA,EAAA,OAAA,EA9IgB,WA8IhB,EAAA,CAAA,EAAA,IAAA;SAIE,CAAA,UAAA,EAAA,MAAA,EAAA,OAAA,EAlIX,WAkIW,EAAA,GAAA,CAAA,CAAA,KAAA,EAlIc,WAkId,EAAA,GAlI8B,WAkI9B,EAAA,CAAA,CAAA,EAAA,IAAA;kBAMP,CAAA,IAAA,EAAA,MAAA,CAAA,EAAA,IAAA;eA2GD,CAAA,GAAA,EAlOP,MAAA,CAAK,eAkOE,EAAA,GAAA,EAjOP,MAAA,CAAK,cAiOE,EAAA,QAAA,EAAA,MAAA,CAAA,EA/NX,OA+NW,CAAA,OAAA,CAAA;QAaC,CAAA,CAAA,EAAA;IA3SkB,MAAA,EAAA,MAAA;IAAS,WAAA,EAAA,MAAA;;sBAyKtB;wBAIE;WAMP;UA2GD;iBAaC"}
@@ -1,4 +1,4 @@
1
- import { flattenHeaders, readBody } from "./helpers.js";
1
+ import { flattenHeaders, isJsonObject, readBody } from "./helpers.js";
2
2
  import { createVectorRequestHandler } from "./vector-handler.js";
3
3
  import * as http$1 from "node:http";
4
4
 
@@ -58,10 +58,40 @@ var VectorMock = class {
58
58
  if (body) parsed = JSON.parse(body);
59
59
  } catch (parseErr) {
60
60
  const detail = parseErr instanceof Error ? parseErr.message : "unknown";
61
+ if (this.journal) this.journal.add({
62
+ method: req.method ?? "GET",
63
+ path: req.url ?? "/",
64
+ headers: flattenHeaders(req.headers),
65
+ body: null,
66
+ service: "vector",
67
+ response: {
68
+ status: 400,
69
+ fixture: null
70
+ }
71
+ });
61
72
  res.writeHead(400, { "Content-Type": "application/json" });
62
73
  res.end(JSON.stringify({ error: `Malformed JSON body: ${detail}` }));
63
74
  return true;
64
75
  }
76
+ if (!isJsonObject(parsed)) {
77
+ if (this.journal) this.journal.add({
78
+ method: req.method ?? "GET",
79
+ path: req.url ?? "/",
80
+ headers: flattenHeaders(req.headers),
81
+ body: null,
82
+ service: "vector",
83
+ response: {
84
+ status: 400,
85
+ fixture: null
86
+ }
87
+ });
88
+ res.writeHead(400, { "Content-Type": "application/json" });
89
+ res.end(JSON.stringify({ error: {
90
+ message: "Request body must be a JSON object",
91
+ type: "invalid_request_error"
92
+ } }));
93
+ return true;
94
+ }
65
95
  }
66
96
  const handled = this.requestHandler(req, res, pathname, parsed);
67
97
  if (handled && this.registry) {
@@ -82,6 +112,23 @@ var VectorMock = class {
82
112
  fixture: null
83
113
  }
84
114
  });
115
+ if (!handled && this.journal) {
116
+ const journal = this.journal;
117
+ res.once("finish", () => {
118
+ if (res.statusCode !== 404) return;
119
+ journal.add({
120
+ method: req.method ?? "GET",
121
+ path: req.url ?? "/",
122
+ headers: flattenHeaders(req.headers),
123
+ body: null,
124
+ service: "vector",
125
+ response: {
126
+ status: 404,
127
+ fixture: null
128
+ }
129
+ });
130
+ });
131
+ }
85
132
  return handled;
86
133
  }
87
134
  health() {
@@ -112,11 +159,41 @@ var VectorMock = class {
112
159
  } catch (parseErr) {
113
160
  if (req.method !== "GET") {
114
161
  const detail = parseErr instanceof Error ? parseErr.message : "unknown";
162
+ if (this.journal) this.journal.add({
163
+ method: req.method ?? "GET",
164
+ path: req.url ?? "/",
165
+ headers: flattenHeaders(req.headers),
166
+ body: null,
167
+ service: "vector",
168
+ response: {
169
+ status: 400,
170
+ fixture: null
171
+ }
172
+ });
115
173
  res.writeHead(400, { "Content-Type": "application/json" });
116
174
  res.end(JSON.stringify({ error: `Malformed JSON body: ${detail}` }));
117
175
  return;
118
176
  }
119
177
  }
178
+ if (req.method !== "GET" && !isJsonObject(parsed)) {
179
+ if (this.journal) this.journal.add({
180
+ method: req.method ?? "GET",
181
+ path: req.url ?? "/",
182
+ headers: flattenHeaders(req.headers),
183
+ body: null,
184
+ service: "vector",
185
+ response: {
186
+ status: 400,
187
+ fixture: null
188
+ }
189
+ });
190
+ res.writeHead(400, { "Content-Type": "application/json" });
191
+ res.end(JSON.stringify({ error: {
192
+ message: "Request body must be a JSON object",
193
+ type: "invalid_request_error"
194
+ } }));
195
+ return;
196
+ }
120
197
  const url = new URL(req.url ?? "/", `http://${host}`);
121
198
  const handled = this.requestHandler(req, res, url.pathname, parsed);
122
199
  if (handled && this.journal) this.journal.add({
@@ -131,6 +208,17 @@ var VectorMock = class {
131
208
  }
132
209
  });
133
210
  if (!handled) {
211
+ if (this.journal) this.journal.add({
212
+ method: req.method ?? "GET",
213
+ path: req.url ?? "/",
214
+ headers: flattenHeaders(req.headers),
215
+ body: null,
216
+ service: "vector",
217
+ response: {
218
+ status: 404,
219
+ fixture: null
220
+ }
221
+ });
134
222
  res.writeHead(404, { "Content-Type": "application/json" });
135
223
  res.end(JSON.stringify({ error: "Not found" }));
136
224
  }
@@ -1 +1 @@
1
- {"version":3,"file":"vector-mock.js","names":["http"],"sources":["../src/vector-mock.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport type { Mountable, JournalEntry } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type {\n VectorMockOptions,\n VectorCollection,\n VectorEntry,\n QueryResult,\n VectorQuery,\n QueryHandler,\n} from \"./vector-types.js\";\nimport { createVectorRequestHandler, type VectorState } from \"./vector-handler.js\";\nimport { flattenHeaders, readBody } from \"./helpers.js\";\n\nexport class VectorMock implements Mountable {\n private collections: Map<string, VectorCollection> = new Map();\n private queryHandlers: Map<string, QueryHandler> = new Map();\n private server: http.Server | null = null;\n private journal: Journal | null = null;\n private registry: MetricsRegistry | null = null;\n private options: VectorMockOptions;\n private requestHandler: ReturnType<typeof createVectorRequestHandler>;\n\n constructor(options?: VectorMockOptions) {\n this.options = options ?? {};\n this.requestHandler = this.buildHandler();\n }\n\n // ---- Configuration ----\n\n addCollection(name: string, opts: { dimension: number }): this {\n const collection: VectorCollection = {\n name,\n dimension: opts.dimension,\n vectors: new Map(),\n };\n this.collections.set(name, collection);\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n upsert(collection: string, vectors: VectorEntry[]): this {\n let col = this.collections.get(collection);\n if (!col) {\n const dim = vectors.length > 0 ? vectors[0].values.length : 0;\n col = { name: collection, dimension: dim, vectors: new Map() };\n this.collections.set(collection, col);\n }\n for (const v of vectors) {\n col.vectors.set(v.id, v);\n }\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n onQuery(\n collection: string,\n results: QueryResult[] | ((query: VectorQuery) => QueryResult[]),\n ): this {\n this.queryHandlers.set(collection, results);\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n deleteCollection(name: string): this {\n this.collections.delete(name);\n this.queryHandlers.delete(name);\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n // ---- Mountable interface ----\n\n async handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n ): Promise<boolean> {\n // Only consume the request body for methods that carry a body,\n // avoiding double-consumption when the route doesn't match.\n let parsed: Record<string, unknown> = {};\n if (req.method !== \"GET\" && req.method !== \"DELETE\" && req.method !== \"HEAD\") {\n const body = await readBody(req);\n try {\n if (body) parsed = JSON.parse(body);\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Malformed JSON body: ${detail}` }));\n return true;\n }\n }\n\n const handled = this.requestHandler(req, res, pathname, parsed);\n\n // Record vector operation metric\n if (handled && this.registry) {\n const { operation, provider } = classifyVectorRequest(req.method ?? \"GET\", pathname);\n this.registry.incrementCounter(\"aimock_vector_requests_total\", { operation, provider });\n }\n\n // Journal the request after the handler completes\n if (handled && this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n\n return handled;\n }\n\n health(): { status: string; collections: number } {\n return {\n status: \"ok\",\n collections: this.collections.size,\n };\n }\n\n setJournal(journal: Journal): void {\n this.journal = journal;\n }\n\n setRegistry(registry: MetricsRegistry): void {\n this.registry = registry;\n }\n\n // ---- Standalone mode ----\n\n async start(): Promise<string> {\n if (this.server) {\n throw new Error(\"Server already started\");\n }\n\n const host = this.options.host ?? \"127.0.0.1\";\n const port = this.options.port ?? 0;\n\n return new Promise((resolve, reject) => {\n const srv = http.createServer((req, res) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => {\n const body = Buffer.concat(chunks).toString();\n let parsed: Record<string, unknown> = {};\n try {\n if (body) parsed = JSON.parse(body);\n } catch (parseErr) {\n if (req.method !== \"GET\") {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Malformed JSON body: ${detail}` }));\n return;\n }\n }\n\n const url = new URL(req.url ?? \"/\", `http://${host}`);\n\n const handled = this.requestHandler(req, res, url.pathname, parsed);\n\n if (handled && this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n if (!handled) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not found\" }));\n }\n });\n });\n\n srv.listen(port, host, () => {\n this.server = srv;\n const addr = srv.address();\n if (typeof addr === \"object\" && addr !== null) {\n resolve(`http://${host}:${addr.port}`);\n } else {\n resolve(`http://${host}:${port}`);\n }\n });\n\n srv.on(\"error\", reject);\n });\n }\n\n async stop(): Promise<void> {\n if (!this.server) {\n throw new Error(\"Server not started\");\n }\n const srv = this.server;\n this.server = null;\n await new Promise<void>((resolve, reject) => {\n srv.close((err) => (err ? reject(err) : resolve()));\n });\n }\n\n // ---- Inspection ----\n\n getRequests(): JournalEntry[] {\n if (!this.journal) return [];\n return this.journal.getAll().filter((e) => e.service === \"vector\");\n }\n\n reset(): this {\n this.collections.clear();\n this.queryHandlers.clear();\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n // ---- Internal ----\n\n private buildHandler() {\n const state: VectorState = {\n collections: this.collections,\n queryHandlers: this.queryHandlers,\n };\n return createVectorRequestHandler(state);\n }\n}\n\n// ---- Helpers ----\n\n/**\n * Classify a vector request by operation and provider based on HTTP method and pathname.\n */\nfunction classifyVectorRequest(\n method: string,\n pathname: string,\n): { operation: string; provider: string } {\n // Pinecone paths\n if (pathname === \"/query\" && method === \"POST\") {\n return { operation: \"query\", provider: \"pinecone\" };\n }\n if (pathname === \"/vectors/upsert\" && method === \"POST\") {\n return { operation: \"upsert\", provider: \"pinecone\" };\n }\n if (pathname === \"/vectors/delete\" && method === \"POST\") {\n return { operation: \"delete\", provider: \"pinecone\" };\n }\n if (pathname === \"/describe-index-stats\" && method === \"GET\") {\n return { operation: \"describe\", provider: \"pinecone\" };\n }\n\n // Qdrant paths\n if (/^\\/collections\\/[^/]+\\/points\\/search$/.test(pathname) && method === \"POST\") {\n return { operation: \"query\", provider: \"qdrant\" };\n }\n if (/^\\/collections\\/[^/]+\\/points$/.test(pathname) && method === \"PUT\") {\n return { operation: \"upsert\", provider: \"qdrant\" };\n }\n if (/^\\/collections\\/[^/]+\\/points\\/delete$/.test(pathname) && method === \"POST\") {\n return { operation: \"delete\", provider: \"qdrant\" };\n }\n\n // ChromaDB paths\n if (/^\\/api\\/v1\\/collections\\/[^/]+\\/query$/.test(pathname) && method === \"POST\") {\n return { operation: \"query\", provider: \"chromadb\" };\n }\n if (/^\\/api\\/v1\\/collections\\/[^/]+\\/add$/.test(pathname) && method === \"POST\") {\n return { operation: \"upsert\", provider: \"chromadb\" };\n }\n if (pathname === \"/api/v1/collections\" && method === \"GET\") {\n return { operation: \"list\", provider: \"chromadb\" };\n }\n if (/^\\/api\\/v1\\/collections\\/[^/]+$/.test(pathname) && method === \"DELETE\") {\n return { operation: \"delete\", provider: \"chromadb\" };\n }\n\n return { operation: \"unknown\", provider: \"unknown\" };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"vector-mock.js","names":["http"],"sources":["../src/vector-mock.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport type { Mountable, JournalEntry } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type {\n VectorMockOptions,\n VectorCollection,\n VectorEntry,\n QueryResult,\n VectorQuery,\n QueryHandler,\n} from \"./vector-types.js\";\nimport { createVectorRequestHandler, type VectorState } from \"./vector-handler.js\";\nimport { flattenHeaders, isJsonObject, readBody } from \"./helpers.js\";\n\nexport class VectorMock implements Mountable {\n private collections: Map<string, VectorCollection> = new Map();\n private queryHandlers: Map<string, QueryHandler> = new Map();\n private server: http.Server | null = null;\n private journal: Journal | null = null;\n private registry: MetricsRegistry | null = null;\n private options: VectorMockOptions;\n private requestHandler: ReturnType<typeof createVectorRequestHandler>;\n\n constructor(options?: VectorMockOptions) {\n this.options = options ?? {};\n this.requestHandler = this.buildHandler();\n }\n\n // ---- Configuration ----\n\n addCollection(name: string, opts: { dimension: number }): this {\n const collection: VectorCollection = {\n name,\n dimension: opts.dimension,\n vectors: new Map(),\n };\n this.collections.set(name, collection);\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n upsert(collection: string, vectors: VectorEntry[]): this {\n let col = this.collections.get(collection);\n if (!col) {\n const dim = vectors.length > 0 ? vectors[0].values.length : 0;\n col = { name: collection, dimension: dim, vectors: new Map() };\n this.collections.set(collection, col);\n }\n for (const v of vectors) {\n col.vectors.set(v.id, v);\n }\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n onQuery(\n collection: string,\n results: QueryResult[] | ((query: VectorQuery) => QueryResult[]),\n ): this {\n this.queryHandlers.set(collection, results);\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n deleteCollection(name: string): this {\n this.collections.delete(name);\n this.queryHandlers.delete(name);\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n // ---- Mountable interface ----\n\n async handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n pathname: string,\n ): Promise<boolean> {\n // Only consume the request body for methods that carry a body,\n // avoiding double-consumption when the route doesn't match.\n let parsed: Record<string, unknown> = {};\n if (req.method !== \"GET\" && req.method !== \"DELETE\" && req.method !== \"HEAD\") {\n const body = await readBody(req);\n try {\n if (body) parsed = JSON.parse(body);\n } catch (parseErr) {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: 400, fixture: null },\n });\n }\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Malformed JSON body: ${detail}` }));\n return true;\n }\n\n // Reject bodies that parsed but are not a JSON object (e.g. `null`)\n // before the route handlers touch fields — `body.ids`, `body.vectors`,\n // `body.points` and friends throw a TypeError on `null` that nothing\n // catches, killing the process rather than answering the request.\n // Arrays and scalars are rejected for the same reason the JSON handlers\n // reject them: every field reads as `undefined` and the handler answers\n // a misleading `200` on a degenerate empty result.\n if (!isJsonObject(parsed)) {\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: 400, fixture: null },\n });\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 true;\n }\n }\n\n const handled = this.requestHandler(req, res, pathname, parsed);\n\n // Record vector operation metric\n if (handled && this.registry) {\n const { operation, provider } = classifyVectorRequest(req.method ?? \"GET\", pathname);\n this.registry.incrementCounter(\"aimock_vector_requests_total\", { operation, provider });\n }\n\n // Journal the request after the handler completes\n if (handled && this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n\n // When mounted, an unmatched path is answered by the parent server, not\n // here, so journal it once the response is out and only if it really was\n // a 404 — otherwise a path this wrapper declines but the server rewrites\n // and serves would be journaled twice. Keeps mounted mode in step with\n // the standalone 404 journaling below.\n if (!handled && this.journal) {\n const journal = this.journal;\n res.once(\"finish\", () => {\n if (res.statusCode !== 404) return;\n journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: 404, fixture: null },\n });\n });\n }\n\n return handled;\n }\n\n health(): { status: string; collections: number } {\n return {\n status: \"ok\",\n collections: this.collections.size,\n };\n }\n\n setJournal(journal: Journal): void {\n this.journal = journal;\n }\n\n setRegistry(registry: MetricsRegistry): void {\n this.registry = registry;\n }\n\n // ---- Standalone mode ----\n\n async start(): Promise<string> {\n if (this.server) {\n throw new Error(\"Server already started\");\n }\n\n const host = this.options.host ?? \"127.0.0.1\";\n const port = this.options.port ?? 0;\n\n return new Promise((resolve, reject) => {\n const srv = http.createServer((req, res) => {\n const chunks: Buffer[] = [];\n req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n req.on(\"end\", () => {\n const body = Buffer.concat(chunks).toString();\n let parsed: Record<string, unknown> = {};\n try {\n if (body) parsed = JSON.parse(body);\n } catch (parseErr) {\n if (req.method !== \"GET\") {\n const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: 400, fixture: null },\n });\n }\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Malformed JSON body: ${detail}` }));\n return;\n }\n }\n\n // Same non-object rejection as the mounted path above — a `null`\n // body otherwise reaches the route handlers and throws an uncaught\n // TypeError that takes the standalone server's process down.\n if (req.method !== \"GET\" && !isJsonObject(parsed)) {\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: 400, fixture: null },\n });\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 url = new URL(req.url ?? \"/\", `http://${host}`);\n\n const handled = this.requestHandler(req, res, url.pathname, parsed);\n\n if (handled && this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: res.statusCode, fixture: null },\n });\n }\n if (!handled) {\n if (this.journal) {\n this.journal.add({\n method: req.method ?? \"GET\",\n path: req.url ?? \"/\",\n headers: flattenHeaders(req.headers),\n body: null,\n service: \"vector\",\n response: { status: 404, fixture: null },\n });\n }\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Not found\" }));\n }\n });\n });\n\n srv.listen(port, host, () => {\n this.server = srv;\n const addr = srv.address();\n if (typeof addr === \"object\" && addr !== null) {\n resolve(`http://${host}:${addr.port}`);\n } else {\n resolve(`http://${host}:${port}`);\n }\n });\n\n srv.on(\"error\", reject);\n });\n }\n\n async stop(): Promise<void> {\n if (!this.server) {\n throw new Error(\"Server not started\");\n }\n const srv = this.server;\n this.server = null;\n await new Promise<void>((resolve, reject) => {\n srv.close((err) => (err ? reject(err) : resolve()));\n });\n }\n\n // ---- Inspection ----\n\n getRequests(): JournalEntry[] {\n if (!this.journal) return [];\n return this.journal.getAll().filter((e) => e.service === \"vector\");\n }\n\n reset(): this {\n this.collections.clear();\n this.queryHandlers.clear();\n this.requestHandler = this.buildHandler();\n return this;\n }\n\n // ---- Internal ----\n\n private buildHandler() {\n const state: VectorState = {\n collections: this.collections,\n queryHandlers: this.queryHandlers,\n };\n return createVectorRequestHandler(state);\n }\n}\n\n// ---- Helpers ----\n\n/**\n * Classify a vector request by operation and provider based on HTTP method and pathname.\n */\nfunction classifyVectorRequest(\n method: string,\n pathname: string,\n): { operation: string; provider: string } {\n // Pinecone paths\n if (pathname === \"/query\" && method === \"POST\") {\n return { operation: \"query\", provider: \"pinecone\" };\n }\n if (pathname === \"/vectors/upsert\" && method === \"POST\") {\n return { operation: \"upsert\", provider: \"pinecone\" };\n }\n if (pathname === \"/vectors/delete\" && method === \"POST\") {\n return { operation: \"delete\", provider: \"pinecone\" };\n }\n if (pathname === \"/describe-index-stats\" && method === \"GET\") {\n return { operation: \"describe\", provider: \"pinecone\" };\n }\n\n // Qdrant paths\n if (/^\\/collections\\/[^/]+\\/points\\/search$/.test(pathname) && method === \"POST\") {\n return { operation: \"query\", provider: \"qdrant\" };\n }\n if (/^\\/collections\\/[^/]+\\/points$/.test(pathname) && method === \"PUT\") {\n return { operation: \"upsert\", provider: \"qdrant\" };\n }\n if (/^\\/collections\\/[^/]+\\/points\\/delete$/.test(pathname) && method === \"POST\") {\n return { operation: \"delete\", provider: \"qdrant\" };\n }\n\n // ChromaDB paths\n if (/^\\/api\\/v1\\/collections\\/[^/]+\\/query$/.test(pathname) && method === \"POST\") {\n return { operation: \"query\", provider: \"chromadb\" };\n }\n if (/^\\/api\\/v1\\/collections\\/[^/]+\\/add$/.test(pathname) && method === \"POST\") {\n return { operation: \"upsert\", provider: \"chromadb\" };\n }\n if (pathname === \"/api/v1/collections\" && method === \"GET\") {\n return { operation: \"list\", provider: \"chromadb\" };\n }\n if (/^\\/api\\/v1\\/collections\\/[^/]+$/.test(pathname) && method === \"DELETE\") {\n return { operation: \"delete\", provider: \"chromadb\" };\n }\n\n return { operation: \"unknown\", provider: \"unknown\" };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"vector-types.d.cts","names":[],"sources":["../src/vector-types.ts"],"sourcesContent":[],"mappings":";UAAiB,iBAAA;EAAA,IAAA,CAAA,EAAA,MAAA;EAKA,IAAA,CAAA,EAAA,MAAA;;AAGM,UAHN,gBAAA,CAGM;MAAZ,EAAA,MAAA;EAAG,SAAA,EAAA,MAAA;EAGG,OAAA,EAHN,GAGM,CAAA,MAAW,EAHL,WAMJ,CAAA;AAGnB;AAOiB,UAbA,WAAA,CAaW;EAOhB,EAAA,EAAA,MAAA;EAAY,MAAA,EAAA,MAAA,EAAA;UAAG,CAAA,EAjBd,MAiBc,CAAA,MAAA,EAAA,OAAA,CAAA;;AAAyC,UAdnD,WAAA,CAcmD;EAAW,EAAA,EAAA,MAAA;;aAXlE;;;UAII,WAAA;;;;;;KAOL,YAAA,GAAe,yBAAyB,gBAAgB"}
1
+ {"version":3,"file":"vector-types.d.cts","names":[],"sources":["../src/vector-types.ts"],"sourcesContent":[],"mappings":";UAAiB,iBAAA;EAAA,IAAA,CAAA,EAAA,MAAA;EAKA,IAAA,CAAA,EAAA,MAAA;;AAGM,UAHN,gBAAA,CAGM;MAAZ,EAAA,MAAA;EAAG,SAAA,EAAA,MAAA;EAGG,OAAA,EAHN,GAGM,CAAA,MAAW,EAHL,WAMV,CAAM;AAGnB;AAOiB,UAbA,WAAA,CAaW;EAOhB,EAAA,EAAA,MAAA;EAAY,MAAA,EAAA,MAAA,EAAA;UAAG,CAAA,EAjBd,MAiBc,CAAA,MAAA,EAAA,OAAA,CAAA;;AAAyC,UAdnD,WAAA,CAcmD;EAAW,EAAA,EAAA,MAAA;;aAXlE;;;UAII,WAAA;;;;;;KAOL,YAAA,GAAe,yBAAyB,gBAAgB"}
@@ -192,7 +192,7 @@ async function handleVeoVideoCreate(req, res, raw, model, fixtures, journal, def
192
192
  journal.incrementFixtureMatchCount(fixture, fixtures, testId);
193
193
  defaults.logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);
194
194
  } else defaults.logger.debug(`No fixture matched for Veo request (model=${model}, msg="${prompt.slice(0, 80)}")`);
195
- if (require_chaos.applyChaos(res, fixture, defaults.chaos, req.headers, journal, {
195
+ if (require_chaos.applyChaos(res, fixture, defaults.chaos, req.headers, req.url, journal, {
196
196
  method,
197
197
  path,
198
198
  headers: require_helpers.flattenHeaders(req.headers),
@@ -312,7 +312,7 @@ async function handleVeoVideoStatus(req, res, operationName, fixtures, journal,
312
312
  setCorsHeaders(res);
313
313
  const path = req.url ?? `/v1beta/${operationName}`;
314
314
  const method = req.method ?? "GET";
315
- if (require_chaos.applyChaos(res, null, defaults.chaos, req.headers, journal, {
315
+ if (require_chaos.applyChaos(res, null, defaults.chaos, req.headers, req.url, journal, {
316
316
  method,
317
317
  path,
318
318
  headers: require_helpers.flattenHeaders(req.headers),